mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-16 09:53:36 +00:00
Class and instance definitions both contain lists of methods,
predicates and/or functions, that each have one or more procedures.
Until now, we represented the methods in class and instance definitions
as lists of nothing more than pred_proc_ids. This fact complicated
several operations,
- partly because there was no simple way to tell which procedures
were part of the same predicate or function, and
- partly because the order of the list is important (we identify
each method procedure in our equivalent of vtables with a number,
which is simply the procedure's position in this list), but there was
absolutely no information about recorded about this.
This diff therefore replaces the lists of pred_proc_ids with lists of
method_infos. Each method_info contains
- the method procedure number, i.e. the vtable index,
- the pred_or_func, sym_name and user arity of the predicate or function
that the method procedure is a part of, to make it simple to test
whether two method_infos represent different modes of the same predicate
or function, or not,
- the original pred_proc_id of the method procedure, which never changes,
and
- the current pred_proc_id, which program transformations *can* change.
compiler/hlds_class.m:
Make the change above in the representations of class and instance
definitions.
Put the fields of both types into a better order, by putting
related fields next to each other.
Put a notag wrapper around method procedure numbers to prevent
accidentally mixing them up with plain integers.
Add some utility functions.
compiler/prog_data.m:
Replace three fields containing pred_or_func, sym_name and arity
in the parse tree representation of instance methods with just one,
which contains all three pieces of info. This makes it easier to operate
on them as a unit.
Change the representation of methods defined by clauses from a list
of clauses to a cord of clauses, since this supports constant-time
append.
compiler/hlds_goal.m:
Switch from plain ints to the new notag representation of method
procedure numbers in method call goals.
compiler/add_class.m:
Simplify the code for adding new classes to the HLDS.
Give some predicates better names.
compiler/check_typeclass.m:
Significantly simplify the code for that generates the pred_infos and
proc_infos implementing all the methods of an instances definition,
and construct lists of method_infos instead of lists of pred_proc_ids.
Give some predicates better names.
Some error messages about problems in instance definitions started with
In instance declaration for class/arity:
while others started with
In instance declaration for class(module_a.foo, module_b.bar):
Replace both with
In instance declaration for class(foo, bar):
because it contains more useful information than the first, and less
non-useful information than the second. Improve the wording of some
error messages.
Factor out some common code.
compiler/prog_mode.m:
compiler/prog_type.m:
compiler/prog_util.m:
Generalize the existing predicates for stripping "builtin.m" module
qualifiers from sym_names, cons_ids, insts, types and modes
to allow also the stripping of *all* module qualifiers. This capability
is now used when we print an instance's type vector as a context
for diagnostics about problems inside instance definitions.
compiler/add_pred.m:
Add a mechanism for returning the pred_id of a newly created pred_info,
whether or not it was declared using a predmode declaration. This
capability is now needed by add_class.m.
Move the code creating an error message into its own function, and export
that function for add_class.m.
compiler/polymorphism_type_info.m:
Fix some comment rot.
compiler/base_typeclass_info.m:
compiler/call_gen.m:
compiler/dead_proc_elim.m:
compiler/deep_profiling.m:
compiler/direct_arg_in_out.m:
compiler/error_msg_inst.m:
compiler/float_regs.m:
compiler/get_dependencies.m:
compiler/higher_order.m:
compiler/hlds_error_util.m:
compiler/hlds_out_goal.m:
compiler/hlds_out_typeclass_table.m:
compiler/instance_method_clauses.m:
compiler/intermod.m:
compiler/make_hlds_error.m:
compiler/ml_call_gen.m:
compiler/mode_errors.m:
compiler/modes.m:
compiler/module_qual.qualify_items.m:
compiler/old_type_constraints.m:
compiler/parse_class.m:
compiler/parse_tree_out.m:
compiler/parse_tree_out_inst.m:
compiler/polymorphism_post_copy.m:
compiler/polymorphism_type_class_info.m:
compiler/prog_item.m:
compiler/prog_rep.m:
compiler/recompilation.usage.m:
compiler/state_var.m:
compiler/type_class_info.m:
compiler/typecheck_debug.m:
compiler/typecheck_error_type_assign.m:
compiler/typecheck_errors.m:
compiler/typecheck_msgs.m:
compiler/unused_imports.m:
compiler/xml_documentation.m:
Conform to the changes above.
tests/invalid/bug476.err_exp:
tests/invalid/tc_err1.err_exp:
tests/invalid/tc_err2.err_exp:
tests/invalid/typeclass_bogus_method.err_exp:
tests/invalid/typeclass_missing_mode.err_exp:
tests/invalid/typeclass_missing_mode_2.err_exp:
tests/invalid/typeclass_mode.err_exp:
tests/invalid/typeclass_mode_2.err_exp:
tests/invalid/typeclass_mode_3.err_exp:
tests/invalid/typeclass_mode_4.err_exp:
tests/invalid/typeclass_test_10.err_exp:
tests/invalid/typeclass_test_3.err_exp:
tests/invalid/typeclass_test_4.err_exp:
tests/invalid/typeclass_test_5.err_exp:
tests/invalid/typeclass_test_9.err_exp:
Expect the updated wording of some error messages.
2368 lines
104 KiB
Mathematica
2368 lines
104 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2005-2011,2014 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: state_var.m.
|
|
% Main author of original version: rafe.
|
|
% Main author of the current version, rewritten in 2011: zs.
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module hlds.make_hlds.state_var.
|
|
:- interface.
|
|
|
|
:- import_module hlds.hlds_goal.
|
|
:- import_module hlds.make_hlds.goal_expr_to_goal.
|
|
:- import_module libs.
|
|
:- import_module libs.globals.
|
|
:- import_module mdbcomp.
|
|
:- import_module mdbcomp.prim_data.
|
|
:- import_module mdbcomp.sym_name.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.error_spec.
|
|
:- import_module parse_tree.prog_data.
|
|
|
|
:- import_module list.
|
|
:- import_module map.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% This synonym improves code legibility. The intention is that we use
|
|
% svar instead of prog_var in pred type declarations for any variables X
|
|
% that represent state variables !X.
|
|
%
|
|
:- type svar == prog_var.
|
|
|
|
% When collecting the arms of a disjunction, we also need to collect
|
|
% the resulting svar_states.
|
|
%
|
|
:- type hlds_goal_svar_state
|
|
---> hlds_goal_svar_state(hlds_goal, svar_state).
|
|
|
|
% The state of the currently visible state variables. The state gets
|
|
% updated differently along differently execution paths. When execution
|
|
% paths rejoin, you need to create the state after the rejoin from the
|
|
% states being rejoined (which is what we use hlds_goal_svar_state for)
|
|
% using their last common ancestor state as a basis.
|
|
:- type svar_state.
|
|
|
|
% The persistent information needed by the state variable transformation.
|
|
% The store should always be threaded straight through all computations
|
|
% involved in the translation of the parse tree to the HLDS, with all
|
|
% updates being permanent.
|
|
:- type svar_store.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Replace !X args with two args !.X, !:X in that order.
|
|
%
|
|
:- pred expand_bang_state_pairs_in_terms(list(prog_term)::in,
|
|
list(prog_term)::out) is det.
|
|
:- pred expand_bang_state_pairs_in_instance_method(instance_method::in,
|
|
instance_method::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Prepare for processing a clause by processing its head.
|
|
% If the head contains any references to !.S or !:S or both,
|
|
% make state variable S known in the body of the clause.
|
|
% (The head should not contain any references to !S; those should
|
|
% have been expanded out by calling expand_bang_state_pairs BEFORE calling
|
|
% this predicate.)
|
|
%
|
|
% Given the original list of args, we return a version in which state
|
|
% variable references have been replaced. Since we don't yet know what
|
|
% the final values of the state variables will be, we create prog_vars
|
|
% to represent these values, and return a mapping from the state vars
|
|
% to these designated-final-value prog_vars.
|
|
%
|
|
:- pred svar_prepare_for_clause_head(list(prog_term)::in, list(prog_term)::out,
|
|
prog_varset::in, prog_varset::out, map(svar, prog_var)::out,
|
|
svar_state::out, svar_store::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
% Prepare for processing a lambda expression by processing its head.
|
|
%
|
|
% In most ways, this is very similar to processing the head of a clause,
|
|
% but we also need to handle state variables which are visible in the scope
|
|
% that encloses the lambda expression. We make those state vars read-only
|
|
% within the lambda expression.
|
|
%
|
|
:- pred svar_prepare_for_lambda_head(prog_context::in,
|
|
list(prog_term)::in, list(prog_term)::out,
|
|
map(svar, prog_var)::out, svar_state::in, svar_state::out,
|
|
prog_varset::in, prog_varset::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
% Finish processing a clause. Make the final values of the clause's state
|
|
% vars match the mapping we decided on when processing the head.
|
|
%
|
|
:- pred svar_finish_clause_body(globals::in, module_name::in, prog_context::in,
|
|
map(svar, prog_var)::in, hlds_goal::in, hlds_goal::in, hlds_goal::out,
|
|
svar_state::in, svar_state::in, svar_store::in,
|
|
list(error_spec)::out, list(error_spec)::out) is det.
|
|
|
|
% Finish processing a lambda expression.
|
|
%
|
|
:- pred svar_finish_lambda_body(globals::in, module_name::in, prog_context::in,
|
|
map(svar, prog_var)::in, list(hlds_goal)::in, hlds_goal::out,
|
|
svar_state::in, svar_state::in, svar_store::in, svar_store::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Finish the execution of an atomic goal. If this goal was not inside
|
|
% another atomic goal, then make any updates to state variables performed
|
|
% by the atomic goal take effect: make the value assigned to !:S inside
|
|
% the goal the new !.S.
|
|
%
|
|
:- pred svar_finish_atomic_goal(loc_kind::in, svar_state::in, svar_state::out)
|
|
is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Add some local state variables.
|
|
%
|
|
:- pred svar_prepare_for_local_state_vars(prog_context::in, prog_varset::in,
|
|
list(svar)::in, svar_state::in, svar_state::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
% Remove some local state variables.
|
|
%
|
|
:- pred svar_finish_local_state_vars(globals::in, module_name::in,
|
|
list(svar)::in, svar_state::in, svar_state::in, svar_state::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Make sure that all arms of a disjunction produce the same state variable
|
|
% bindings, by adding unifiers as necessary.
|
|
%
|
|
:- pred svar_finish_disjunction(list(hlds_goal_svar_state)::in,
|
|
list(hlds_goal)::out,
|
|
prog_varset::in, prog_varset::out, svar_state::in, svar_state::out,
|
|
svar_store::in, svar_store::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Add unifiers to the Then and Else arms of an if-then-else to make sure
|
|
% that all the state variables match up.
|
|
%
|
|
% We also add unifiers to the Then arm for any new state variable
|
|
% mappings produced in the condition.
|
|
%
|
|
:- pred svar_finish_if_then_else(globals::in, module_name::in,
|
|
loc_kind::in, prog_context::in, list(svar)::in,
|
|
hlds_goal::in, hlds_goal::out, hlds_goal::in, hlds_goal::out,
|
|
svar_state::in, svar_state::in, svar_state::in, svar_state::in,
|
|
svar_state::out, prog_varset::in, prog_varset::out,
|
|
svar_store::in, svar_store::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type svar_outer_atomic_scope_info.
|
|
:- type svar_inner_atomic_scope_info.
|
|
|
|
% svar_start_outer_atomic_scope(Context, OuterStateVar, OuterDI, OuterUO,
|
|
% OuterScopeInfo, !State, !VarSet, !Specs):
|
|
%
|
|
% This predicate converts a !OuterStateVar specification in an atomic scope
|
|
% to a pair of outer state variables, OuterDI and OuterUO. Since
|
|
% !OuterStateVar should *not* be accessible inside the atomic scope,
|
|
% we delete it, but record it in OuterScopeInfo. The accessibility of
|
|
% !OuterStateVar will be restored when you call svar_finish_atomic_scope
|
|
% with OuterScopeInfo.
|
|
%
|
|
:- pred svar_start_outer_atomic_scope(prog_context::in, prog_var::in,
|
|
prog_var::out, prog_var::out, svar_outer_atomic_scope_info::out,
|
|
svar_state::in, svar_state::out, prog_varset::in, prog_varset::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
% svar_finish_outer_atomic_scope(OuterScopeInfo, !SInfo):
|
|
%
|
|
% Restore the accessibility of !OuterStateVar that was disabled by
|
|
% svar_start_atomic_scope.
|
|
%
|
|
:- pred svar_finish_outer_atomic_scope(svar_outer_atomic_scope_info::in,
|
|
svar_state::in, svar_state::out) is det.
|
|
|
|
% svar_start_inner_atomic_scope(Context, InnerStateVar, InnerScopeInfo,
|
|
% !State, !VarSet, !Specs):
|
|
%
|
|
% This predicate prepares for an atomic scope with an !InnerStateVar
|
|
% specification by making that state var available.
|
|
%
|
|
:- pred svar_start_inner_atomic_scope(prog_context::in, prog_var::in,
|
|
svar_inner_atomic_scope_info::out,
|
|
svar_state::in, svar_state::out, prog_varset::in, prog_varset::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
% svar_finish_inner_atomic_scope(Context, InnerScopeInfo, InnerDI, InnerUO,
|
|
% !State, !VarSet, !Specs):
|
|
%
|
|
% This predicate ends an atomic scope with an !InnerStateVar
|
|
% specification by making that state var unavailable, and returning
|
|
% the two variables InnerDI and InnerUO representing the initial and final
|
|
% states of this state variable.
|
|
%
|
|
:- pred svar_finish_inner_atomic_scope(prog_context::in,
|
|
svar_inner_atomic_scope_info::in, prog_var::out, prog_var::out,
|
|
svar_state::in, svar_state::out, prog_varset::in, prog_varset::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Given a list of argument terms, substitute !.X and !:X with the
|
|
% corresponding state variable mappings. Any !X should already have been
|
|
% expanded into !.X, !:X via a call to expand_bang_state_pairs.
|
|
%
|
|
:- pred substitute_state_var_mappings(
|
|
list(prog_term)::in, list(prog_term)::out,
|
|
prog_varset::in, prog_varset::out,
|
|
svar_state::in, svar_state::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
% Same as substitute_state_var_mappings, but for only one term.
|
|
%
|
|
:- pred substitute_state_var_mapping(prog_term::in, prog_term::out,
|
|
prog_varset::in, prog_varset::out, svar_state::in, svar_state::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
% Look up the prog_var that represents the current state of the given
|
|
% state variable.
|
|
%
|
|
:- pred lookup_dot_state_var(prog_context::in, svar::in, prog_var::out,
|
|
prog_varset::in, prog_varset::out, svar_state::in, svar_state::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
% Look up the prog_var that represents the next state of the given
|
|
% state variable.
|
|
%
|
|
:- pred lookup_colon_state_var(prog_context::in, svar::in, prog_var::out,
|
|
prog_varset::in, prog_varset::out, svar_state::in, svar_state::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Flatten a conjunction while preserving the invariants that the state
|
|
% variable transformation cares about.
|
|
%
|
|
:- pred svar_flatten_conj(prog_context::in,
|
|
list(hlds_goal)::in, hlds_goal::out,
|
|
svar_store::in, svar_store::out) is det.
|
|
|
|
% Flatten a goal into a conjunction while preserving the invariants that
|
|
% the state variable transformation cares about.
|
|
%
|
|
:- pred svar_goal_to_conj_list(hlds_goal::in, list(hlds_goal)::out,
|
|
svar_store::in, svar_store::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% Does the given argument list have a function result term
|
|
% that tries to use state var notation to refer to *two* terms?
|
|
%
|
|
% If yes, return the state variable involved, and the context of the
|
|
% reference.
|
|
%
|
|
:- pred illegal_state_var_func_result(pred_or_func::in, list(prog_term)::in,
|
|
svar::out, prog_context::out) is semidet.
|
|
|
|
% Does the given term have the form a !X, i.e. does it represent
|
|
% *two* arguments? This is not acceptable in some contexts, such as
|
|
% function results and lambda expression arguments.
|
|
%
|
|
% If yes, return the state variable involved, and the context of the
|
|
% reference.
|
|
%
|
|
:- pred is_term_a_bang_state_pair(prog_term::in,
|
|
svar::out, prog_context::out) is semidet.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred report_illegal_state_var_update(prog_context::in,
|
|
string::in, prog_context::in, prog_varset::in,
|
|
svar::in, list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
:- pred report_illegal_func_svar_result(prog_context::in, prog_varset::in,
|
|
svar::in, list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
:- pred report_illegal_bang_svar_lambda_arg(prog_context::in, prog_varset::in,
|
|
svar::in, list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
:- pred report_svar_unify_error(prog_context::in, svar::in,
|
|
prog_varset::in, prog_varset::out, svar_state::in, svar_state::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module hlds.goal_util.
|
|
:- import_module hlds.make_goal.
|
|
:- import_module libs.options.
|
|
:- import_module mdbcomp.goal_path.
|
|
:- import_module parse_tree.error_util.
|
|
:- import_module parse_tree.prog_item.
|
|
:- import_module parse_tree.prog_util.
|
|
:- import_module parse_tree.set_of_var.
|
|
|
|
:- import_module assoc_list.
|
|
:- import_module bool.
|
|
:- import_module cord.
|
|
:- import_module counter.
|
|
:- import_module io.
|
|
:- import_module maybe.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
:- import_module string.
|
|
:- import_module term.
|
|
:- import_module term_context.
|
|
:- import_module varset.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Define the main data structures used by the implementation of state vars.
|
|
%
|
|
|
|
% State vars defined outside a lambda goal become readonly when we move
|
|
% inside the lambda goal. Inside the lambda goal, it makes sense to access
|
|
% the current value of such state vars, but not to update it.
|
|
%
|
|
% We should make negations behave similarly: it should not be possible
|
|
% to update an outside state var inside a negation. However, for now,
|
|
% the language reference manual allows such updates. This type is here
|
|
% in case that changes.
|
|
:- type readonly_context_kind
|
|
---> roc_lambda.
|
|
|
|
:- type svar_status
|
|
% The two updated statuses may legally be present in a status map
|
|
% only DURING the processing of an atomic goal. At the end of each
|
|
% atomic goal, such statuses are always reset to status_known.
|
|
---> status_unknown
|
|
% We are in a scope that allows use of this state var,
|
|
% but it has not been given a value yet. This could be because
|
|
% the scope of the state var was established with !:S, not !S
|
|
% or !.S, in a clause head, or because it was established
|
|
% in a `some [!S]' scope.
|
|
|
|
; status_unknown_updated(prog_var)
|
|
% Before this atomic goal, this state var was status_unknown,
|
|
% but it was initialized by the current atomic goal to the given
|
|
% prog_var.
|
|
|
|
; status_known_ro(prog_var, readonly_context_kind, prog_context)
|
|
% The given prog_var is the current version of this state var,
|
|
% but the variable is readonly (ro); the program CANNOT create
|
|
% new versions of the state var. The second argument says WHY
|
|
% new versions cannot be created, and the third says where
|
|
% the construct named by the second argument occurs.
|
|
|
|
; status_known(prog_var)
|
|
% The given prog_var is the current version of this state var;
|
|
% the program can create new versions of the state var,
|
|
% but has not done so yet.
|
|
|
|
; status_known_updated(prog_var, prog_var).
|
|
% The first prog_var is the current version of this state var,
|
|
% and the second is the new, updated version, which will become
|
|
% the current version when we finish executing the current
|
|
% atomic goal.
|
|
|
|
:- type svar_state
|
|
---> svar_state(
|
|
state_status_map :: map(svar, svar_status)
|
|
).
|
|
|
|
:- type svar_store
|
|
---> svar_store(
|
|
store_next_goal_id :: counter,
|
|
store_final_remap :: incremental_rename_map,
|
|
store_specs :: list(error_spec)
|
|
).
|
|
|
|
% Create a new svar_state/store set up to start processing a clause head.
|
|
%
|
|
:- func new_svar_state = svar_state.
|
|
:- func new_svar_store = svar_store.
|
|
|
|
new_svar_state = svar_state(map.init).
|
|
new_svar_store = svar_store(counter.init(1), map.init, []).
|
|
|
|
:- type state_var_name_source
|
|
---> name_initial
|
|
; name_middle
|
|
; name_final.
|
|
|
|
:- pred new_state_var_instance(svar::in, state_var_name_source::in,
|
|
prog_var::out, prog_varset::in, prog_varset::out) is det.
|
|
|
|
new_state_var_instance(StateVar, NameSource, Var, !VarSet) :-
|
|
SVarName = varset.lookup_name(!.VarSet, StateVar),
|
|
(
|
|
NameSource = name_initial,
|
|
ProgVarName = string.format("STATE_VARIABLE_%s_0", [s(SVarName)]),
|
|
varset.new_named_var(ProgVarName, Var, !VarSet)
|
|
;
|
|
NameSource = name_middle,
|
|
ProgVarBaseName = string.format("STATE_VARIABLE_%s", [s(SVarName)]),
|
|
varset.new_uniquely_named_var(ProgVarBaseName, Var, !VarSet)
|
|
;
|
|
NameSource = name_final,
|
|
ProgVarName = string.format("STATE_VARIABLE_%s", [s(SVarName)]),
|
|
varset.new_named_var(ProgVarName, Var, !VarSet)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Expand !S into !.S, !:S pairs.
|
|
%
|
|
|
|
expand_bang_state_pairs_in_terms([], []).
|
|
expand_bang_state_pairs_in_terms([HeadArg0 | TailArgs0], Args) :-
|
|
expand_bang_state_pairs_in_terms(TailArgs0, TailArgs),
|
|
(
|
|
HeadArg0 = variable(_, _),
|
|
Args = [HeadArg0 | TailArgs]
|
|
;
|
|
HeadArg0 = functor(Const, FunctorArgs, Context),
|
|
( if
|
|
Const = atom("!"),
|
|
FunctorArgs = [variable(_StateVar, _)]
|
|
then
|
|
HeadArg1 = functor(atom("!."), FunctorArgs, Context),
|
|
HeadArg2 = functor(atom("!:"), FunctorArgs, Context),
|
|
Args = [HeadArg1, HeadArg2 | TailArgs]
|
|
else
|
|
Args = [HeadArg0 | TailArgs]
|
|
)
|
|
).
|
|
|
|
expand_bang_state_pairs_in_instance_method(IM0, IM) :-
|
|
IM0 = instance_method(MethodId0, ProcDef0, Context),
|
|
MethodId0 = pred_pf_name_arity(PredOrFunc, MethodSymName, _UserArity0),
|
|
(
|
|
ProcDef0 = instance_proc_def_name(_),
|
|
IM = IM0
|
|
;
|
|
ProcDef0 = instance_proc_def_clauses(ItemClausesCord0),
|
|
cord.map_pred(expand_bang_state_pairs_in_clause,
|
|
ItemClausesCord0, ItemClausesCord),
|
|
% Note that ItemClausesCord0 should never be empty...
|
|
( if cord.head(ItemClausesCord, ItemClause) then
|
|
Args = ItemClause ^ cl_head_args,
|
|
PredFormArity = arg_list_arity(Args),
|
|
user_arity_pred_form_arity(PredOrFunc, UserArity, PredFormArity),
|
|
MethodId = pred_pf_name_arity(PredOrFunc, MethodSymName, UserArity)
|
|
else
|
|
MethodId = MethodId0
|
|
),
|
|
ProcDef = instance_proc_def_clauses(ItemClausesCord),
|
|
IM = instance_method(MethodId, ProcDef, Context)
|
|
).
|
|
|
|
:- pred expand_bang_state_pairs_in_clause(item_clause_info::in,
|
|
item_clause_info::out) is det.
|
|
|
|
expand_bang_state_pairs_in_clause(ItemClause0, ItemClause) :-
|
|
ItemClause0 = item_clause_info(PredOrFunc, SymName, Args0, VarSet,
|
|
MaybeBody, Context, SeqNum),
|
|
expand_bang_state_pairs_in_terms(Args0, Args),
|
|
ItemClause = item_clause_info(PredOrFunc, SymName, Args, VarSet,
|
|
MaybeBody, Context, SeqNum).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Handle the start of processing a clause.
|
|
%
|
|
|
|
svar_prepare_for_clause_head(Args0, Args, !VarSet, FinalMap,
|
|
!:State, !:Store, !Specs) :-
|
|
!:State = new_svar_state,
|
|
!:Store = new_svar_store,
|
|
svar_prepare_head_terms(Args0, Args, map.init, FinalMap,
|
|
!State, !VarSet, !Specs).
|
|
|
|
:- pred svar_prepare_head_terms(list(prog_term)::in, list(prog_term)::out,
|
|
map(svar, prog_var)::in, map(svar, prog_var)::out,
|
|
svar_state::in, svar_state::out, prog_varset::in, prog_varset::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
svar_prepare_head_terms([], [], !FinalMap, !State, !VarSet, !Specs).
|
|
svar_prepare_head_terms([Term0 | Terms0], [Term | Terms],
|
|
!FinalMap, !State, !VarSet, !Specs) :-
|
|
svar_prepare_head_term(Term0, Term, !FinalMap, !State, !VarSet, !Specs),
|
|
svar_prepare_head_terms(Terms0, Terms, !FinalMap, !State, !VarSet, !Specs).
|
|
|
|
:- pred svar_prepare_head_term(prog_term::in, prog_term::out,
|
|
map(svar, prog_var)::in, map(svar, prog_var)::out,
|
|
svar_state::in, svar_state::out,
|
|
prog_varset::in, prog_varset::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
svar_prepare_head_term(Term0, Term, !FinalMap, !State, !VarSet, !Specs) :-
|
|
(
|
|
Term0 = variable(_, _),
|
|
Term = Term0
|
|
;
|
|
Term0 = functor(Functor, SubTerms0, Context),
|
|
( if
|
|
Functor = atom("!."),
|
|
SubTerms0 = [variable(StateVar, _)]
|
|
then
|
|
!.State = svar_state(StatusMap0),
|
|
( if map.search(StatusMap0, StateVar, OldStatus) then
|
|
(
|
|
OldStatus = status_unknown,
|
|
% !:S happened to precede !.S in the head, which is ok.
|
|
new_state_var_instance(StateVar, name_initial, Var,
|
|
!VarSet),
|
|
Term = variable(Var, Context),
|
|
Status = status_known(Var),
|
|
map.det_update(StateVar, Status, StatusMap0, StatusMap)
|
|
;
|
|
OldStatus = status_known(Var),
|
|
Term = variable(Var, Context),
|
|
StatusMap = StatusMap0
|
|
;
|
|
OldStatus = status_unknown_updated(_),
|
|
unexpected($pred, "status_unknown_updated for !.")
|
|
;
|
|
OldStatus = status_known_updated(_, _),
|
|
unexpected($pred, "status_known_updated for !.")
|
|
;
|
|
OldStatus = status_known_ro(_, _, _),
|
|
% This can happen if the context outside a lambda
|
|
% expression has a state variable named StateVar,
|
|
% which make_svars_read_only has given this status,
|
|
% and the lambda expression itself also has !.StateVar.
|
|
new_state_var_instance(StateVar, name_initial, Var,
|
|
!VarSet),
|
|
Term = variable(Var, Context),
|
|
Status = status_known(Var),
|
|
map.det_update(StateVar, Status, StatusMap0, StatusMap)
|
|
)
|
|
else
|
|
new_state_var_instance(StateVar, name_initial, Var, !VarSet),
|
|
Term = variable(Var, Context),
|
|
Status = status_known(Var),
|
|
map.det_insert(StateVar, Status, StatusMap0, StatusMap)
|
|
),
|
|
!:State = svar_state(StatusMap)
|
|
else if
|
|
Functor = atom("!:"),
|
|
SubTerms0 = [variable(StateVar, _)]
|
|
then
|
|
new_state_var_instance(StateVar, name_final, Var, !VarSet),
|
|
Term = variable(Var, Context),
|
|
Status = status_unknown,
|
|
|
|
!.State = svar_state(StatusMap0),
|
|
( if map.search(StatusMap0, StateVar, OldStatus) then
|
|
(
|
|
OldStatus = status_unknown,
|
|
% This is the second occurrence of !:StateVar.
|
|
% Since !.FinalMap will contain StateVar, we will generate
|
|
% the error message below.
|
|
StatusMap = StatusMap0
|
|
;
|
|
OldStatus = status_known(_),
|
|
% The !. part of this state var has already been processed.
|
|
% We have nothing more to do.
|
|
StatusMap = StatusMap0
|
|
;
|
|
OldStatus = status_unknown_updated(_),
|
|
unexpected($pred, "status_unknown_updated for !:")
|
|
;
|
|
OldStatus = status_known_updated(_, _),
|
|
unexpected($pred, "status_known_updated for !:")
|
|
;
|
|
OldStatus = status_known_ro(_, _, _),
|
|
% This can happen if the context outside a lambda
|
|
% expression has a state variable named StateVar,
|
|
% which make_svars_read_only has given this status,
|
|
% and the lambda expression itself also has !:StateVar.
|
|
map.det_update(StateVar, Status, StatusMap0, StatusMap)
|
|
)
|
|
else
|
|
map.det_insert(StateVar, Status, StatusMap0, StatusMap)
|
|
),
|
|
!:State = svar_state(StatusMap),
|
|
map.search_insert(StateVar, Var, MaybeOldVar, !FinalMap),
|
|
(
|
|
MaybeOldVar = yes(_),
|
|
report_repeated_head_state_var(Context, !.VarSet, StateVar,
|
|
!Specs)
|
|
;
|
|
MaybeOldVar = no
|
|
)
|
|
else
|
|
svar_prepare_head_terms(SubTerms0, SubTerms,
|
|
!FinalMap, !State, !VarSet, !Specs),
|
|
Term = functor(Functor, SubTerms, Context)
|
|
)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Handle the start of processing a lambda expression.
|
|
%
|
|
|
|
svar_prepare_for_lambda_head(Context, Args0, Args, FinalMap,
|
|
OutsideState, InsideState, !VarSet, !Specs) :-
|
|
% Make all currently visible state vars readonly, since they cannot
|
|
% be updated inside the lambda expression.
|
|
%
|
|
% Note that some of these state vars may already be readonly, since
|
|
% we may already be inside e.g. a lambda expression. We must make sure
|
|
% that readonly references work even from code that is inside two or more
|
|
% lambda expressions.
|
|
OutsideState = svar_state(OutsideStatusMap),
|
|
map.to_sorted_assoc_list(OutsideStatusMap, OutsideStatusList),
|
|
make_svars_read_only(roc_lambda, Context,
|
|
OutsideStatusList, InsideStatusList),
|
|
map.from_sorted_assoc_list(InsideStatusList, InsideStatusMap),
|
|
InsideState0 = svar_state(InsideStatusMap),
|
|
|
|
% Handle the arguments of the lambda expression as if they were the head
|
|
% of a clause.
|
|
svar_prepare_head_terms(Args0, Args, map.init, FinalMap,
|
|
InsideState0, InsideState, !VarSet, !Specs).
|
|
|
|
:- pred make_svars_read_only(readonly_context_kind::in, prog_context::in,
|
|
assoc_list(svar, svar_status)::in, assoc_list(svar, svar_status)::out)
|
|
is det.
|
|
|
|
make_svars_read_only(_ROC, _Context, [], []).
|
|
make_svars_read_only(ROC, Context, [SVar - CurStatus | CurTail], LambdaList) :-
|
|
make_svars_read_only(ROC, Context, CurTail, LambdaTail),
|
|
(
|
|
( CurStatus = status_unknown
|
|
; CurStatus = status_unknown_updated(_)
|
|
),
|
|
LambdaList = LambdaTail
|
|
;
|
|
CurStatus = status_known_ro(_, _, _),
|
|
LambdaList = [SVar - CurStatus | LambdaTail]
|
|
;
|
|
( CurStatus = status_known(Var)
|
|
; CurStatus = status_known_updated(Var, _)
|
|
),
|
|
LambdaStatus = status_known_ro(Var, ROC, Context),
|
|
LambdaList = [SVar - LambdaStatus | LambdaTail]
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Handle the end of processing a clause or lambda expression.
|
|
%
|
|
|
|
svar_finish_clause_body(Globals, ModuleName, Context, FinalMap,
|
|
HeadGoal0, BodyGoal0, Goal, InitialSVarState, FinalSVarState,
|
|
!.SVarStore, WarningSpecs, ErrorSpecs) :-
|
|
svar_finish_body(Globals, ModuleName, Context, FinalMap,
|
|
[HeadGoal0, BodyGoal0], Goal1, InitialSVarState, FinalSVarState,
|
|
!SVarStore),
|
|
!.SVarStore = svar_store(_, DelayedRenamings, Specs),
|
|
list.filter(severity_is_error(Globals), Specs, ErrorSpecs, WarningSpecs),
|
|
( if
|
|
map.is_empty(FinalMap),
|
|
map.is_empty(DelayedRenamings)
|
|
then
|
|
Goal = Goal1
|
|
else
|
|
trace [compiletime(flag("state-var-lambda")), io(!IO)] (
|
|
get_debug_output_stream(Globals, ModuleName, DebugStream, !IO),
|
|
map.to_assoc_list(FinalMap, FinalList),
|
|
map.to_assoc_list(DelayedRenamings, DelayedList),
|
|
io.write_string(DebugStream,
|
|
"\nFINISH CLAUSE BODY in context ", !IO),
|
|
io.write_line(DebugStream, Context, !IO),
|
|
io.write_string(DebugStream, "applying subn\n", !IO),
|
|
io.write_line(DebugStream, FinalList, !IO),
|
|
io.write_string(DebugStream, "with incremental subn\n", !IO),
|
|
io.write_line(DebugStream, DelayedList, !IO)
|
|
),
|
|
incremental_rename_vars_in_goal(map.init, DelayedRenamings,
|
|
Goal1, Goal2),
|
|
|
|
% Suppose we have a goal such as this:
|
|
%
|
|
% ( if p(..., !.S, !:S) then
|
|
% <then_part>
|
|
% else
|
|
% <else_part>
|
|
% )
|
|
%
|
|
% and that !:S is not referred to anywhere in the clause
|
|
% (not in then_part, not in else_part, not in the following code).
|
|
%
|
|
% In this form, warn_singletons can generate a warning about !:S
|
|
% being a singleton. However, the state variable transformation
|
|
% transforms it to code like this:
|
|
%
|
|
% ( if p(..., STATE_VARIABLE_S_5, STATE_VARIABLE_S_6) then
|
|
% <then_part>, STATE_VARIABLE_S_7 = STATE_VARIABLE_S_6
|
|
% else
|
|
% <else_part>, STATE_VARIABLE_S_7 = STATE_VARIABLE_S_5
|
|
% )
|
|
%
|
|
% and marks both assignments to STATE_VARIABLE_S_7 as not being
|
|
% subject to singleton warnings.
|
|
%
|
|
% We don't actually *want* to generate warnings about the assignments
|
|
% to STATE_VARIABLE_S_7, because the problem is not in those
|
|
% assignments or in the if-then-else arms that contain them.
|
|
% Instead, it is in the condition. However, with this version
|
|
% of the code, the occurrence of STATE_VARIABLE_S_6 in the condition
|
|
% is *not* a singleton.
|
|
%
|
|
% To allow us to generate a warning about !:S in the condition,
|
|
% we delete all copy unifications inserted by the state variable
|
|
% transformation that assign to a variable that is not referred to
|
|
% either in the code that follows the assignment, or in the head.
|
|
% (The head contains the output arguments, which will live
|
|
% beyond the lifetime of anything in the clause body.)
|
|
%
|
|
% To find which variables occur after a copy goal, we have
|
|
% delete_unneeded_copy_goals do a backwards traversal of the clause
|
|
% body, keeping track of all the variables it has seen.
|
|
%
|
|
% To find which variables occur in the head, we use the call to
|
|
% goal_vars below. The variables in HeadGoal0 contain not just
|
|
% the head_vars of the clause, but also the state variable instances
|
|
% any of them are unified with. (This includes the input arguments
|
|
% as well as the output arguments, but the clause body won't contain
|
|
% any assignments to either the input arguments or the state variable
|
|
% instances they are unified with, so including them in SeenLater0
|
|
% is harmless. As it happens, we have to include them because we
|
|
% don't know which arguments are input and which are output,
|
|
% a distinction that in any case may be mode-dependent.)
|
|
%
|
|
% We cannot count on the definitions of those state var instances
|
|
% being before any of the occurrences of the head vars they are
|
|
% unified with. For example, if a fact contains an !S argument pair,
|
|
% the call to svar_finish_body above will put the unification of
|
|
% the state var instances representing !.S and !:S *after*
|
|
% HeadGoal0. If we initialized SeenLater0 to just the head_vars
|
|
% of the clause, this unification would assign to a variable
|
|
% that is *not* in SeenLater0, and would thus be eliminated,
|
|
% which would be a bug.
|
|
goal_vars(HeadGoal0, HeadGoal0Vars),
|
|
SeenLater0 = HeadGoal0Vars,
|
|
delete_unneeded_copy_goals(Goal2, Goal, SeenLater0, _SeenLater)
|
|
).
|
|
|
|
svar_finish_lambda_body(Globals, ModuleName, Context, FinalMap, Goals0, Goal,
|
|
InitialSVarState, FinalSVarState, !SVarStore) :-
|
|
svar_finish_body(Globals, ModuleName, Context, FinalMap, Goals0, Goal,
|
|
InitialSVarState, FinalSVarState, !SVarStore).
|
|
|
|
:- pred svar_finish_body(globals::in, module_name::in, prog_context::in,
|
|
map(svar, prog_var)::in, list(hlds_goal)::in, hlds_goal::out,
|
|
svar_state::in, svar_state::in, svar_store::in, svar_store::out) is det.
|
|
|
|
svar_finish_body(Globals, ModuleName, Context, FinalMap, Goals0, Goal,
|
|
InitialSVarState, FinalSVarState, !Store) :-
|
|
map.to_assoc_list(FinalMap, FinalAssocList),
|
|
InitialSVarState = svar_state(InitialSVarStatusMap),
|
|
FinalSVarState = svar_state(FinalSVarStatusMap),
|
|
svar_find_final_renames_and_copy_goals(FinalAssocList,
|
|
InitialSVarStatusMap, FinalSVarStatusMap,
|
|
[], FinalSVarSubn, [], CopyGoals),
|
|
(
|
|
CopyGoals = [],
|
|
Goals1 = Goals0
|
|
;
|
|
CopyGoals = [_ | _],
|
|
Goals1 = Goals0 ++ CopyGoals
|
|
),
|
|
svar_flatten_conj(Context, Goals1, Goal1, !Store),
|
|
|
|
Goal1 = hlds_goal(GoalExpr1, GoalInfo1),
|
|
GoalId1 = goal_info_get_goal_id(GoalInfo1),
|
|
!.Store = svar_store(NextGoalId1, DelayedRenamingMap1, Specs),
|
|
( if map.search(DelayedRenamingMap1, GoalId1, DelayedRenaming0) then
|
|
trace [compiletime(flag("state-var-lambda")), io(!IO)] (
|
|
get_debug_output_stream(Globals, ModuleName, DebugStream, !IO),
|
|
io.write_string(DebugStream, "\nfinishing body, ", !IO),
|
|
io.write_string(DebugStream,
|
|
"attaching subn to existing goal_id ", !IO),
|
|
io.write_line(DebugStream, GoalId1, !IO),
|
|
io.write_string(DebugStream, "subn is ", !IO),
|
|
io.write_line(DebugStream, FinalSVarSubn, !IO)
|
|
),
|
|
|
|
map.det_update(GoalId1, DelayedRenaming0 ++ FinalSVarSubn,
|
|
DelayedRenamingMap1, DelayedRenamingMap),
|
|
NextGoalId = NextGoalId1,
|
|
Goal = Goal1
|
|
else
|
|
(
|
|
FinalSVarSubn = [],
|
|
NextGoalId = NextGoalId1,
|
|
DelayedRenamingMap = DelayedRenamingMap1,
|
|
Goal = Goal1
|
|
;
|
|
FinalSVarSubn = [_ | _],
|
|
counter.allocate(GoalIdNum, NextGoalId1, NextGoalId),
|
|
GoalId = goal_id(GoalIdNum),
|
|
|
|
trace [compiletime(flag("state-var-lambda")), io(!IO)] (
|
|
get_debug_output_stream(Globals, ModuleName, DebugStream, !IO),
|
|
io.write_string(DebugStream, "\nfinishing body, ", !IO),
|
|
io.write_string(DebugStream,
|
|
"attaching subn to new goal_id ", !IO),
|
|
io.write_line(DebugStream, GoalId, !IO),
|
|
io.write_string(DebugStream, "subn is ", !IO),
|
|
io.write_line(DebugStream, FinalSVarSubn, !IO)
|
|
),
|
|
|
|
map.det_insert(GoalId, FinalSVarSubn,
|
|
DelayedRenamingMap1, DelayedRenamingMap),
|
|
goal_info_set_goal_id(GoalId, GoalInfo1, GoalInfo),
|
|
Goal = hlds_goal(GoalExpr1, GoalInfo)
|
|
)
|
|
),
|
|
!:Store = svar_store(NextGoalId, DelayedRenamingMap, Specs).
|
|
|
|
:- pred svar_find_final_renames_and_copy_goals(assoc_list(svar, prog_var)::in,
|
|
map(svar, svar_status)::in, map(svar, svar_status)::in,
|
|
assoc_list(prog_var, prog_var)::in, assoc_list(prog_var, prog_var)::out,
|
|
list(hlds_goal)::in, list(hlds_goal)::out) is det.
|
|
|
|
svar_find_final_renames_and_copy_goals([], _, _, !FinalSVarSubn, !CopyGoals).
|
|
svar_find_final_renames_and_copy_goals([Head | Tail],
|
|
InitialStatusMap, FinalStatusMap, !FinalSVarSubn, !CopyGoals) :-
|
|
Head = SVar - FinalHeadVar,
|
|
map.lookup(InitialStatusMap, SVar, InitialStatus),
|
|
map.lookup(FinalStatusMap, SVar, FinalStatus),
|
|
(
|
|
FinalStatus = status_known(LastVar),
|
|
( if FinalStatus = InitialStatus then
|
|
% The state variable was not updated by the body.
|
|
% Leaving the unification between two headvars representing the
|
|
% initial and final states to the done at the start of the clause
|
|
% causes problems at the moment for the mode checker in the
|
|
% presence of unique modes.
|
|
make_copy_goal(LastVar, FinalHeadVar, CopyGoal),
|
|
!:CopyGoals = [CopyGoal | !.CopyGoals]
|
|
else
|
|
!:FinalSVarSubn = [LastVar - FinalHeadVar | !.FinalSVarSubn]
|
|
)
|
|
;
|
|
FinalStatus = status_unknown
|
|
% The state variable was never defined.
|
|
% The clause head already refers to the final version.
|
|
;
|
|
FinalStatus = status_known_ro(_, _, _),
|
|
unexpected($pred, "readonly status")
|
|
;
|
|
( FinalStatus = status_known_updated(_, _)
|
|
; FinalStatus = status_unknown_updated(_)
|
|
),
|
|
unexpected($pred, "updated status")
|
|
),
|
|
svar_find_final_renames_and_copy_goals(Tail,
|
|
InitialStatusMap, FinalStatusMap, !FinalSVarSubn, !CopyGoals).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Handle the completion of an atomic goal. Any variable that was updated in the
|
|
% goal gets the updated value as its new current value. The Loc argument is
|
|
% needed because sometimes what looks like an atomic goal (such as the
|
|
% condition of an if-then-else) is inside another atomic goal (such as an
|
|
% if-then-else expression). In such cases, the end of the inside atomic goal
|
|
% does NOT mean that we finished the containing atomic goal.
|
|
%
|
|
|
|
svar_finish_atomic_goal(Loc, !State) :-
|
|
(
|
|
Loc = loc_whole_goal,
|
|
!.State = svar_state(StatusMap0),
|
|
map.map_values_only(reset_updated_status, StatusMap0, StatusMap),
|
|
!:State = svar_state(StatusMap)
|
|
;
|
|
Loc = loc_inside_atomic_goal
|
|
).
|
|
|
|
:- pred reset_updated_status(svar_status::in, svar_status::out) is det.
|
|
|
|
reset_updated_status(!Status) :-
|
|
(
|
|
( !.Status = status_unknown
|
|
; !.Status = status_known_ro(_, _, _)
|
|
; !.Status = status_known(_)
|
|
)
|
|
;
|
|
!.Status = status_unknown_updated(NewProgVar),
|
|
!:Status = status_known(NewProgVar)
|
|
;
|
|
!.Status = status_known_updated(_OldProgVar, NewProgVar),
|
|
!:Status = status_known(NewProgVar)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Handle scopes that introduce state variables.
|
|
%
|
|
|
|
svar_prepare_for_local_state_vars(Context, VarSet, StateVars,
|
|
OutsideState, InsideState, !Specs) :-
|
|
OutsideState = svar_state(StatusMapOutside),
|
|
prepare_svars_for_scope(Context, VarSet, StateVars,
|
|
StatusMapOutside, StatusMapInside, !Specs),
|
|
InsideState = svar_state(StatusMapInside).
|
|
|
|
:- pred prepare_svars_for_scope(prog_context::in, prog_varset::in,
|
|
list(svar)::in, map(svar, svar_status)::in, map(svar, svar_status)::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
prepare_svars_for_scope(_Context, _VarSet, [], !StatusMap, !Specs).
|
|
prepare_svars_for_scope(Context, VarSet, [SVar | SVars],
|
|
!StatusMap, !Specs) :-
|
|
( if map.search(!.StatusMap, SVar, _OldStatus) then
|
|
report_state_var_shadow(Context, VarSet, SVar, !Specs),
|
|
map.det_update(SVar, status_unknown, !StatusMap)
|
|
else
|
|
map.det_insert(SVar, status_unknown, !StatusMap)
|
|
),
|
|
prepare_svars_for_scope(Context, VarSet, SVars, !StatusMap, !Specs).
|
|
|
|
svar_finish_local_state_vars(Globals, ModuleName, StateVars,
|
|
StateBeforeOutside, StateAfterInside, StateAfterOutside) :-
|
|
StateBeforeOutside = svar_state(StatusMapBeforeOutside),
|
|
StateAfterInside = svar_state(StatusMapAfterInside),
|
|
trace [compiletime(flag("state-var-scope")), io(!IO)] (
|
|
get_debug_output_stream(Globals, ModuleName, DebugStream, !IO),
|
|
map.to_assoc_list(StatusMapBeforeOutside, BeforeOutsideStatuses),
|
|
map.to_assoc_list(StatusMapAfterInside, AfterInsideStatuses),
|
|
io.write_string(DebugStream, "Finish of scope\n", !IO),
|
|
io.write_string(DebugStream, "quantified state vars\n", !IO),
|
|
io.write_line(DebugStream, StateVars, !IO),
|
|
io.write_string(DebugStream, "status before outside\n", !IO),
|
|
list.foldl(io.write_line(DebugStream), BeforeOutsideStatuses, !IO),
|
|
io.write_string(DebugStream, "status after inside\n", !IO),
|
|
list.foldl(io.write_line(DebugStream), AfterInsideStatuses, !IO)
|
|
),
|
|
% Remove access to the state vars introduced in the scope.
|
|
% Leave the status of all other state vars unaffected.
|
|
StatusMapAfterOutside0 = StatusMapAfterInside,
|
|
finish_svars_for_scope(StateVars, StatusMapBeforeOutside,
|
|
StatusMapAfterOutside0, StatusMapAfterOutside),
|
|
StateAfterOutside = svar_state(StatusMapAfterOutside).
|
|
|
|
:- pred finish_svars_for_scope(list(svar)::in, map(svar, svar_status)::in,
|
|
map(svar, svar_status)::in, map(svar, svar_status)::out) is det.
|
|
|
|
finish_svars_for_scope([], _, !StatusMapAfterOutside).
|
|
finish_svars_for_scope([SVar | SVars], StatusMapBeforeOutside,
|
|
!StatusMapAfterOutside) :-
|
|
( if map.search(StatusMapBeforeOutside, SVar, BeforeOutsideStatus) then
|
|
% The state var was visible before the scope. The outside state var
|
|
% was shadowed by a state var in the scope. Now that we are leaving
|
|
% the scope, restore access to the outside state var. Due to the
|
|
% shadowing, its status couldn't have changed inside the scope.
|
|
map.det_update(SVar, BeforeOutsideStatus, !StatusMapAfterOutside)
|
|
else
|
|
% The state var introduced in the scope wasn't visible before it.
|
|
map.det_remove(SVar, _, !StatusMapAfterOutside)
|
|
),
|
|
finish_svars_for_scope(SVars, StatusMapBeforeOutside,
|
|
!StatusMapAfterOutside).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Handle disjunctions. The algorithm we use has two passes over the disjuncts.
|
|
%
|
|
% - Pass 1 finds out, for each state variable known at the start of the
|
|
% disjunction, whether it was updated by any arms, and if yes, it picks
|
|
% the final prog_var from one of the updated arms to represent the state var
|
|
% after the disjunction.
|
|
%
|
|
% - Pass 2 processes the arms to ensure that the picked prog_var represents
|
|
% the final value of the state variable in all the arms. In arms that do not
|
|
% update the state variable, it introduces unifications to copy the initial
|
|
% value of the state var to be the final value. In arms that do update the
|
|
% state var, it schedules the prog_var representing the final value in
|
|
% that arm to be renamed to the picked prog_var.
|
|
|
|
svar_finish_disjunction(DisjStates, Disjs, !VarSet,
|
|
StateBefore, StateAfter, !Store) :-
|
|
StateBefore = svar_state(StatusMapBefore),
|
|
( if map.is_empty(StatusMapBefore) then
|
|
% Optimize the common case.
|
|
get_disjuncts_with_empty_states(DisjStates, [], RevDisjs),
|
|
list.reverse(RevDisjs, Disjs),
|
|
StateAfter = StateBefore
|
|
else
|
|
map.to_sorted_assoc_list(StatusMapBefore, StatusListBefore),
|
|
compute_status_after_arms(StatusListBefore, DisjStates,
|
|
map.init, ChangedStatusMapAfter, StatusMapBefore, StatusMapAfter),
|
|
map.to_sorted_assoc_list(ChangedStatusMapAfter,
|
|
ChangedStatusListAfter),
|
|
StateAfter = svar_state(StatusMapAfter),
|
|
|
|
!.Store = svar_store(NextGoalId0, DelayedRenamings0, Specs0),
|
|
merge_changes_made_by_arms(DisjStates, StatusMapBefore,
|
|
ChangedStatusListAfter, !.VarSet, [], RevDisjs,
|
|
NextGoalId0, NextGoalId, DelayedRenamings0, DelayedRenamings,
|
|
Specs0, Specs),
|
|
list.reverse(RevDisjs, Disjs),
|
|
!:Store = svar_store(NextGoalId, DelayedRenamings, Specs)
|
|
).
|
|
|
|
:- pred get_disjuncts_with_empty_states(list(hlds_goal_svar_state)::in,
|
|
list(hlds_goal)::in, list(hlds_goal)::out) is det.
|
|
|
|
get_disjuncts_with_empty_states([], !RevDisjuncts).
|
|
get_disjuncts_with_empty_states([GoalState | GoalStates], !RevDisjuncts) :-
|
|
GoalState = hlds_goal_svar_state(Goal, State),
|
|
StatusMapAfterGoal = State ^ state_status_map,
|
|
expect(map.is_empty(StatusMapAfterGoal), $pred,
|
|
"map after goal not empty"),
|
|
!:RevDisjuncts = [Goal | !.RevDisjuncts],
|
|
get_disjuncts_with_empty_states(GoalStates, !RevDisjuncts).
|
|
|
|
% Pass 1. Compute the changes in the status map.
|
|
%
|
|
:- pred compute_status_after_arms(assoc_list(svar, svar_status)::in,
|
|
list(hlds_goal_svar_state)::in,
|
|
map(svar, svar_status)::in, map(svar, svar_status)::out,
|
|
map(svar, svar_status)::in, map(svar, svar_status)::out) is det.
|
|
|
|
compute_status_after_arms(_StatusListBefore, [],
|
|
!ChangedStatusMapAfter, !StatusMapAfter).
|
|
compute_status_after_arms(StatusListBefore, [ArmState | ArmStates],
|
|
!ChangedStatusMapAfter, !StatusMapAfter) :-
|
|
ArmState = hlds_goal_svar_state(_Armunct, StateAfterArm),
|
|
StatusMapAfterArm = StateAfterArm ^ state_status_map,
|
|
find_changes_in_arm_and_update_changed_status_map(StatusListBefore,
|
|
StatusMapAfterArm, !ChangedStatusMapAfter, !StatusMapAfter),
|
|
compute_status_after_arms(StatusListBefore, ArmStates,
|
|
!ChangedStatusMapAfter, !StatusMapAfter).
|
|
|
|
:- pred find_changes_in_arm_and_update_changed_status_map(
|
|
assoc_list(svar, svar_status)::in, map(svar, svar_status)::in,
|
|
map(svar, svar_status)::in, map(svar, svar_status)::out,
|
|
map(svar, svar_status)::in, map(svar, svar_status)::out) is det.
|
|
|
|
find_changes_in_arm_and_update_changed_status_map([], _,
|
|
!ChangedStatusMapAfter, !StatusMapAfter).
|
|
find_changes_in_arm_and_update_changed_status_map([Before | Befores],
|
|
StatusMapAfterArm, !ChangedStatusMapAfter, !StatusMapAfter) :-
|
|
Before = SVar - StatusBefore,
|
|
map.lookup(StatusMapAfterArm, SVar, StatusAfter),
|
|
( if StatusBefore = StatusAfter then
|
|
true
|
|
else
|
|
( if map.search(!.ChangedStatusMapAfter, SVar, _AlreadyUpdated) then
|
|
true
|
|
else
|
|
map.det_insert(SVar, StatusAfter, !ChangedStatusMapAfter),
|
|
map.det_update(SVar, StatusAfter, !StatusMapAfter)
|
|
)
|
|
),
|
|
find_changes_in_arm_and_update_changed_status_map(Befores,
|
|
StatusMapAfterArm, !ChangedStatusMapAfter, !StatusMapAfter).
|
|
|
|
% Pass 2. Effect the computed changes in the status map.
|
|
%
|
|
:- pred merge_changes_made_by_arms(list(hlds_goal_svar_state)::in,
|
|
map(svar, svar_status)::in, assoc_list(svar, svar_status)::in,
|
|
prog_varset::in, list(hlds_goal)::in, list(hlds_goal)::out,
|
|
counter::in, counter::out,
|
|
incremental_rename_map::in, incremental_rename_map::out,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
merge_changes_made_by_arms([], _StatusMapBefore, _ChangedStatusListAfter,
|
|
_VarSet, !RevArms, !NextGoalId, !DelayedRenamings, !Specs).
|
|
merge_changes_made_by_arms([ArmState | ArmStates],
|
|
StatusMapBefore, ChangedStatusListAfter, VarSet, !RevArms,
|
|
!NextGoalId, !DelayedRenamings, !Specs) :-
|
|
ArmState = hlds_goal_svar_state(Arm0, StateAfterArm),
|
|
StatusMapAfterArm = StateAfterArm ^ state_status_map,
|
|
counter.allocate(ArmIdNum, !NextGoalId),
|
|
ArmId = goal_id(ArmIdNum),
|
|
handle_arm_updated_state_vars(ChangedStatusListAfter, StatusMapBefore,
|
|
StatusMapAfterArm, VarSet, UninitVarNames, CopyGoals, ArmRenames),
|
|
map.det_insert(ArmId, ArmRenames, !DelayedRenamings),
|
|
Arm0 = hlds_goal(ArmExpr0, ArmInfo0),
|
|
(
|
|
CopyGoals = [],
|
|
ArmExpr = ArmExpr0
|
|
;
|
|
CopyGoals = [_ | _],
|
|
svar_goal_to_conj_list_internal(Arm0, ArmGoals0,
|
|
!NextGoalId, !DelayedRenamings),
|
|
ArmExpr = conj(plain_conj, ArmGoals0 ++ CopyGoals)
|
|
),
|
|
(
|
|
UninitVarNames = []
|
|
;
|
|
UninitVarNames = [_ | _],
|
|
% It is ok for an arm that cannot succeed not to initialize
|
|
% a variable, but we record an informational message anyway,
|
|
% to be printed in case the procedure has a mode error.
|
|
ArmContext = goal_info_get_context(ArmInfo0),
|
|
report_missing_inits_in_disjunct(ArmContext, UninitVarNames, !Specs)
|
|
),
|
|
goal_info_set_goal_id(ArmId, ArmInfo0, ArmInfo),
|
|
Arm = hlds_goal(ArmExpr, ArmInfo),
|
|
!:RevArms = [Arm | !.RevArms],
|
|
merge_changes_made_by_arms(ArmStates, StatusMapBefore,
|
|
ChangedStatusListAfter, VarSet, !RevArms,
|
|
!NextGoalId, !DelayedRenamings, !Specs).
|
|
|
|
:- pred handle_arm_updated_state_vars(assoc_list(svar, svar_status)::in,
|
|
map(svar, svar_status)::in, map(svar, svar_status)::in,
|
|
prog_varset::in, list(string)::out,
|
|
list(hlds_goal)::out, assoc_list(prog_var, prog_var)::out) is det.
|
|
|
|
handle_arm_updated_state_vars([], _, _, _, [], [], []).
|
|
handle_arm_updated_state_vars([Change | Changes], StatusMapBefore,
|
|
StatusMapAfterArm, VarSet, UninitVarNames, CopyGoals, Renames) :-
|
|
handle_arm_updated_state_vars(Changes, StatusMapBefore, StatusMapAfterArm,
|
|
VarSet, UninitVarNamesTail, CopyGoalsTail, RenamesTail),
|
|
Change = StateVar - AfterAllArmsStatus,
|
|
map.lookup(StatusMapBefore, StateVar, BeforeStatus),
|
|
map.lookup(StatusMapAfterArm, StateVar, AfterArmStatus),
|
|
( if AfterArmStatus = BeforeStatus then
|
|
expect_not(unify(AfterArmStatus, AfterAllArmsStatus),
|
|
$pred, "AfterArmStatus = AfterAllArmsStatus"),
|
|
(
|
|
% If the state var is readonly in this context, then it shouldn't
|
|
% have been updated by any arms. However, if it was, then we have
|
|
% (a) already generated an error message for it, and (b) changed
|
|
% its status to writeable to suppress duplicate error messages.
|
|
% This is why this code treats known_ro the same as known.
|
|
( BeforeStatus = status_known(BeforeVar)
|
|
; BeforeStatus = status_known_ro(BeforeVar, _, _)
|
|
),
|
|
(
|
|
AfterAllArmsStatus = status_known(AfterAllVar),
|
|
make_copy_goal(BeforeVar, AfterAllVar, CopyGoal),
|
|
CopyGoals = [CopyGoal | CopyGoalsTail],
|
|
UninitVarNames = UninitVarNamesTail,
|
|
Renames = RenamesTail
|
|
;
|
|
( AfterAllArmsStatus = status_known_ro(_, _, _)
|
|
; AfterAllArmsStatus = status_known_updated(_, _)
|
|
; AfterAllArmsStatus = status_unknown
|
|
; AfterAllArmsStatus = status_unknown_updated(_)
|
|
),
|
|
unexpected($pred,
|
|
"AfterAllArmsStatus != status_known (Before == After)")
|
|
)
|
|
;
|
|
BeforeStatus = status_unknown,
|
|
varset.lookup_name(VarSet, StateVar, Name),
|
|
UninitVarName = "!:" ++ Name,
|
|
CopyGoals = CopyGoalsTail,
|
|
UninitVarNames = [UninitVarName | UninitVarNamesTail],
|
|
Renames = RenamesTail
|
|
;
|
|
( BeforeStatus = status_known_updated(_, _)
|
|
; BeforeStatus = status_unknown_updated(_)
|
|
),
|
|
% If the state var was updated before this disjunction,
|
|
% then any reference to !:StateVar should refer to the already
|
|
% known updated prog_var, and thus AfterAllArmsStatus should be
|
|
% the same as StatusBefore, which means we shouldn't get here.
|
|
unexpected($pred, "BeforeStatus is updated")
|
|
)
|
|
else
|
|
(
|
|
AfterArmStatus = status_known(AfterArmVar),
|
|
(
|
|
AfterAllArmsStatus = status_known(AfterAllVar),
|
|
CopyGoals = CopyGoalsTail,
|
|
UninitVarNames = UninitVarNamesTail,
|
|
( if AfterArmVar = AfterAllVar then
|
|
Renames = RenamesTail
|
|
else
|
|
Renames = [AfterArmVar - AfterAllVar | RenamesTail]
|
|
)
|
|
;
|
|
( AfterAllArmsStatus = status_known_ro(_, _, _)
|
|
; AfterAllArmsStatus = status_known_updated(_, _)
|
|
; AfterAllArmsStatus = status_unknown
|
|
; AfterAllArmsStatus = status_unknown_updated(_)
|
|
),
|
|
unexpected($pred,
|
|
"AfterAllArmsStatus != status_known (Before != After)")
|
|
)
|
|
;
|
|
AfterArmStatus = status_known_ro(_, _, _),
|
|
unexpected($pred, "AfterArmStatus = status_known_ro")
|
|
;
|
|
AfterArmStatus = status_known_updated(_, _),
|
|
unexpected($pred, "AfterArmStatus = status_known_updated")
|
|
;
|
|
AfterArmStatus = status_unknown,
|
|
unexpected($pred, "AfterArmStatus = status_unknown")
|
|
;
|
|
AfterArmStatus = status_unknown_updated(_),
|
|
unexpected($pred, "AfterArmStatus = status_unknown")
|
|
)
|
|
).
|
|
|
|
:- pred make_copy_goal(prog_var::in, prog_var::in, hlds_goal::out) is det.
|
|
|
|
make_copy_goal(FromVar, ToVar, CopyGoal) :-
|
|
% We can do the copying in one of two ways. Using unifications
|
|
% can cause problems because the (plain, non-unique) mode analysis pass
|
|
% feels free to schedule them in places where the unique mode analysis pass
|
|
% does not like them; specifically, it can cause a di reference to a
|
|
% variable to appear before a ui reference.
|
|
%
|
|
% The alternative is to add a builtin predicate to the standard library
|
|
% that just does copying, and to make make_copy_goal construct a call to
|
|
% that predicate. That predicate would need to be able to be called in
|
|
% three modes: di/uo, mdi/muo and in/out. However, it needs to have inst
|
|
% parameters so that whatever shape information we have about the source
|
|
% (subtype info, higher order mode info), we copy to the target.
|
|
%
|
|
% We generate a unification, and try to ensure that we don't generate
|
|
% di references to state variables before possible ui references. See the
|
|
% comment in svar_find_final_renames_and_copy_goals before the call to
|
|
% make_copy_goal.
|
|
|
|
create_pure_atomic_complicated_unification(ToVar, rhs_var(FromVar),
|
|
dummy_context, umc_implicit("state variable"), [], CopyGoal0),
|
|
goal_add_features([feature_dont_warn_singleton, feature_state_var_copy],
|
|
CopyGoal0, CopyGoal).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Handle if-then-else goals. The basic idea is the same as for disjunctions,
|
|
% but we also have to handle three complications.
|
|
%
|
|
% First, the first disjunct consists of two parts: the condition and the then
|
|
% part, with data flowing between them.
|
|
%
|
|
% Second, variables can be quantified over the condition and the then part.
|
|
%
|
|
% Third, the if-then-else need not be a goal; it can also be an expression.
|
|
% This means that it is ok for variables to have status known_updated or
|
|
% unknown_updated in any of the status maps we handle.
|
|
%
|
|
|
|
svar_finish_if_then_else(Globals, ModuleName, LocKind, Context, QuantStateVars,
|
|
ThenGoal0, ThenGoal, ElseGoal0, ElseGoal,
|
|
StateBefore, StateAfterCond, StateAfterThen, StateAfterElse,
|
|
StateAfterITE, !VarSet, !Store, !Specs) :-
|
|
StateBefore = svar_state(StatusMapBefore),
|
|
StatusMapAfterCond = StateAfterCond ^ state_status_map,
|
|
StatusMapAfterThen = StateAfterThen ^ state_status_map,
|
|
StatusMapAfterElse = StateAfterElse ^ state_status_map,
|
|
map.keys(StatusMapBefore, SVarsBefore),
|
|
map.keys(StatusMapAfterCond, SVarsAfterCond),
|
|
map.keys(StatusMapAfterThen, SVarsAfterThen),
|
|
map.keys(StatusMapAfterElse, SVarsAfterElse),
|
|
expect(list.sublist(SVarsBefore, SVarsAfterCond), $pred,
|
|
"vars Before not sublist of Cond"),
|
|
expect(unify(SVarsBefore, SVarsAfterThen), $pred,
|
|
"vars Before != AfterThen"),
|
|
expect(unify(SVarsBefore, SVarsAfterElse), $pred,
|
|
"vars Before != AfterElse"),
|
|
|
|
handle_state_vars_in_ite(Globals, ModuleName, LocKind, QuantStateVars,
|
|
SVarsBefore, StatusMapBefore, StatusMapAfterCond,
|
|
StatusMapAfterThen, StatusMapAfterElse,
|
|
map.init, StatusMapAfterITE, !VarSet,
|
|
[], NeckCopyGoals, [], ThenEndCopyGoals, [], ElseEndCopyGoals,
|
|
[], ThenRenames, [], ElseRenames,
|
|
[], ThenMissingInits, [], ElseMissingInits),
|
|
StateAfterITE = svar_state(StatusMapAfterITE),
|
|
|
|
% It is ok for an arm that cannot succeed not to initialize a variable,
|
|
% but we record warnings for them anyway, to be printed in case the
|
|
% procedure has a mode error.
|
|
(
|
|
ThenMissingInits = []
|
|
;
|
|
ThenMissingInits = [_ | _],
|
|
ThenSpecs0 = !.Store ^ store_specs,
|
|
report_missing_inits_in_ite(Context, ThenMissingInits,
|
|
"succeeds", "fails", ThenSpecs0, ThenSpecs),
|
|
!Store ^ store_specs := ThenSpecs
|
|
),
|
|
(
|
|
ElseMissingInits = []
|
|
;
|
|
ElseMissingInits = [_ | _],
|
|
ElseSpecs0 = !.Store ^ store_specs,
|
|
report_missing_inits_in_ite(Context, ElseMissingInits,
|
|
"fails", "succeeds", ElseSpecs0, ElseSpecs),
|
|
!Store ^ store_specs := ElseSpecs
|
|
),
|
|
|
|
svar_goal_to_conj_list(ThenGoal0, ThenGoals0, !Store),
|
|
svar_goal_to_conj_list(ElseGoal0, ElseGoals0, !Store),
|
|
ThenGoals = NeckCopyGoals ++ ThenGoals0 ++ ThenEndCopyGoals,
|
|
ElseGoals = ElseGoals0 ++ ElseEndCopyGoals,
|
|
ThenGoal0 = hlds_goal(_ThenExpr0, ThenInfo0),
|
|
ElseGoal0 = hlds_goal(_ElseExpr0, ElseInfo0),
|
|
conj_list_to_goal(ThenGoals, ThenInfo0, ThenGoal1),
|
|
conj_list_to_goal(ElseGoals, ElseInfo0, ElseGoal1),
|
|
|
|
!.Store = svar_store(NextGoalId0, DelayedRenamings0, Specs),
|
|
counter.allocate(ThenGoalIdNum, NextGoalId0, NextGoalId1),
|
|
counter.allocate(ElseGoalIdNum, NextGoalId1, NextGoalId),
|
|
ThenGoalId = goal_id(ThenGoalIdNum),
|
|
ElseGoalId = goal_id(ElseGoalIdNum),
|
|
goal_set_goal_id(ThenGoalId, ThenGoal1, ThenGoal),
|
|
goal_set_goal_id(ElseGoalId, ElseGoal1, ElseGoal),
|
|
map.det_insert(ThenGoalId, ThenRenames,
|
|
DelayedRenamings0, DelayedRenamings1),
|
|
map.det_insert(ElseGoalId, ElseRenames,
|
|
DelayedRenamings1, DelayedRenamings),
|
|
!:Store = svar_store(NextGoalId, DelayedRenamings, Specs).
|
|
|
|
:- pred handle_state_vars_in_ite(globals::in, module_name::in,
|
|
loc_kind::in, list(svar)::in, list(svar)::in,
|
|
map(svar, svar_status)::in, map(svar, svar_status)::in,
|
|
map(svar, svar_status)::in, map(svar, svar_status)::in,
|
|
map(svar, svar_status)::in, map(svar, svar_status)::out,
|
|
prog_varset::in, prog_varset::out,
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
assoc_list(prog_var, prog_var)::in, assoc_list(prog_var, prog_var)::out,
|
|
assoc_list(prog_var, prog_var)::in, assoc_list(prog_var, prog_var)::out,
|
|
list(string)::in, list(string)::out, list(string)::in, list(string)::out)
|
|
is det.
|
|
|
|
handle_state_vars_in_ite(_, _, _, _, [], _, _, _, _, !StatusMapAfterITE,
|
|
!VarSet, !NeckCopyGoals, !ThenEndCopyGoals, !ElseEndCopyGoals,
|
|
!ThenRenames, !ElseRenames, !ThenMissingInits, !ElseMissingInits).
|
|
handle_state_vars_in_ite(Globals, ModuleName, LocKind, QuantStateVars,
|
|
[SVar | SVars], StatusMapBefore, StatusMapAfterCond,
|
|
StatusMapAfterThen, StatusMapAfterElse, !StatusMapAfterITE,
|
|
!VarSet, !NeckCopyGoals, !ThenEndCopyGoals, !ElseEndCopyGoals,
|
|
!ThenRenames, !ElseRenames, !ThenMissingInits, !ElseMissingInits) :-
|
|
map.lookup(StatusMapBefore, SVar, StatusBefore),
|
|
map.lookup(StatusMapAfterCond, SVar, StatusAfterCond),
|
|
map.lookup(StatusMapAfterThen, SVar, StatusAfterThen),
|
|
map.lookup(StatusMapAfterElse, SVar, StatusAfterElse),
|
|
|
|
( if list.member(SVar, QuantStateVars) then
|
|
expect(unify(StatusBefore, StatusAfterThen), $pred,
|
|
"state var shadowed in if-then-else is nevertheless updated"),
|
|
% SVar is quantified in the if-then-else. That means that Cond and Then
|
|
% may update a state variable with the same name as SVar, but this
|
|
% won't be SVar itself. The status of SVar itself after Cond and after
|
|
% Then will thus be unchanged. This is why we pass StatusBefore
|
|
% not just for itself, but in place of StatusAfterCond and
|
|
% StatusAfterThen as well.
|
|
handle_state_var_in_ite(Globals, ModuleName, LocKind, SVar,
|
|
StatusBefore, StatusBefore, StatusBefore,
|
|
StatusAfterElse, StatusAfterITE,
|
|
!VarSet, !NeckCopyGoals, !ThenEndCopyGoals, !ElseEndCopyGoals,
|
|
!ThenRenames, !ElseRenames, !ThenMissingInits, !ElseMissingInits)
|
|
else
|
|
% If StatusBefore = status_known_ro(_, _, _), then we would expect
|
|
% StatusBefore = StatusAfterCond
|
|
% StatusBefore = StatusAfterThen
|
|
% StatusBefore = StatusAfterElse
|
|
% However, if the user program actually updates a state variable
|
|
% that should be readonly in this scope, then our recovery from that
|
|
% error would invalidate these expectations.
|
|
|
|
handle_state_var_in_ite(Globals, ModuleName, LocKind, SVar,
|
|
StatusBefore, StatusAfterCond, StatusAfterThen,
|
|
StatusAfterElse, StatusAfterITE,
|
|
!VarSet, !NeckCopyGoals, !ThenEndCopyGoals, !ElseEndCopyGoals,
|
|
!ThenRenames, !ElseRenames, !ThenMissingInits, !ElseMissingInits)
|
|
),
|
|
map.det_insert(SVar, StatusAfterITE, !StatusMapAfterITE),
|
|
handle_state_vars_in_ite(Globals, ModuleName, LocKind,
|
|
QuantStateVars, SVars, StatusMapBefore, StatusMapAfterCond,
|
|
StatusMapAfterThen, StatusMapAfterElse, !StatusMapAfterITE,
|
|
!VarSet, !NeckCopyGoals, !ThenEndCopyGoals, !ElseEndCopyGoals,
|
|
!ThenRenames, !ElseRenames, !ThenMissingInits, !ElseMissingInits).
|
|
|
|
:- pred handle_state_var_in_ite(globals::in, module_name::in,
|
|
loc_kind::in, svar::in,
|
|
svar_status::in, svar_status::in, svar_status::in, svar_status::in,
|
|
svar_status::out, prog_varset::in, prog_varset::out,
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
assoc_list(prog_var, prog_var)::in, assoc_list(prog_var, prog_var)::out,
|
|
assoc_list(prog_var, prog_var)::in, assoc_list(prog_var, prog_var)::out,
|
|
list(string)::in, list(string)::out, list(string)::in, list(string)::out)
|
|
is det.
|
|
|
|
handle_state_var_in_ite(Globals, ModuleName, LocKind, SVar, StatusBefore,
|
|
StatusAfterCond, StatusAfterThen, StatusAfterElse, StatusAfterITE,
|
|
!VarSet, !NeckCopyGoals, !ThenEndCopyGoals, !ElseEndCopyGoals,
|
|
!ThenRenames, !ElseRenames, !ThenMissingInits, !ElseMissingInits) :-
|
|
% There are eight cases depending on which of Cond, Then and Else
|
|
% update the state variable:
|
|
%
|
|
% # Cond Then Else Action
|
|
% 1 no no no do nothing
|
|
% 2 no no yes copy at end of then
|
|
% 3 no yes no copy at end of else
|
|
% 4 no yes yes rename else to match then
|
|
% 5 yes no no copy from cond at start of then, copy at end of else
|
|
% 6 yes no yes copy from cond at start of then
|
|
% 7 yes yes no copy at end of else
|
|
% 8 yes yes yes rename else to match then
|
|
|
|
trace [compiletime(flag("state-var-ite")), io(!IO)] (
|
|
get_debug_output_stream(Globals, ModuleName, DebugStream, !IO),
|
|
io.write_string(DebugStream, "state variable ", !IO),
|
|
io.write_line(DebugStream, SVar, !IO),
|
|
io.write_string(DebugStream, "status before: ", !IO),
|
|
io.write_line(DebugStream, StatusBefore, !IO),
|
|
io.write_string(DebugStream, "status after cond: ", !IO),
|
|
io.write_line(DebugStream, StatusAfterCond, !IO),
|
|
io.write_string(DebugStream, "status after then: ", !IO),
|
|
io.write_line(DebugStream, StatusAfterThen, !IO),
|
|
io.write_string(DebugStream, "status after else: ", !IO),
|
|
io.write_line(DebugStream, StatusAfterElse, !IO)
|
|
),
|
|
|
|
( if StatusAfterCond = StatusBefore then
|
|
% Cases 1-4.
|
|
( if StatusAfterThen = StatusAfterCond then
|
|
% Cases 1-2.
|
|
( if StatusAfterElse = StatusBefore then
|
|
% Case 1.
|
|
StatusAfterITE = StatusBefore
|
|
else
|
|
% Case 2.
|
|
(
|
|
StatusBefore = status_known(VarBefore),
|
|
VarAfterElse =
|
|
svar_get_current_progvar(LocKind, StatusAfterElse),
|
|
make_copy_goal(VarBefore, VarAfterElse, CopyGoal),
|
|
!:ThenEndCopyGoals = [CopyGoal | !.ThenEndCopyGoals],
|
|
StatusAfterITE = StatusAfterElse
|
|
;
|
|
StatusBefore = status_unknown,
|
|
varset.lookup_name(!.VarSet, SVar, SVarName),
|
|
!:ThenMissingInits =
|
|
["!:" ++ SVarName | !.ThenMissingInits],
|
|
% We pretend the then part defines StateVar, since this is
|
|
% the right thing to do when the then part cannot succeed.
|
|
% If it can, we will generate an error message during
|
|
% mode analysis.
|
|
StatusAfterITE = StatusAfterElse
|
|
;
|
|
StatusBefore = status_known_ro(_, _, _),
|
|
% The update of !SVar in the else case was an error,
|
|
% for which we have already generated an error message.
|
|
% Because of that, this dummy value won't be used.
|
|
% XXX Returning StatusAfterElse would cause fewer cascading
|
|
% error messages, but are those messages useful or not?
|
|
StatusAfterITE = StatusBefore
|
|
;
|
|
( StatusBefore = status_known_updated(_, _)
|
|
; StatusBefore = status_unknown_updated(_)
|
|
),
|
|
% This can happen if LocKind = loc_inside_atomic_goal,
|
|
% but any reference to !:SVar in the else case should
|
|
% have just returned the new progvar for SVar.
|
|
unexpected($pred, "updated before (case 2)")
|
|
)
|
|
)
|
|
else
|
|
% Cases 3-4.
|
|
( if StatusAfterElse = StatusBefore then
|
|
% Case 3.
|
|
(
|
|
StatusBefore = status_known(VarBefore),
|
|
VarAfterThen =
|
|
svar_get_current_progvar(LocKind, StatusAfterThen),
|
|
make_copy_goal(VarBefore, VarAfterThen, CopyGoal),
|
|
!:ElseEndCopyGoals = [CopyGoal | !.ElseEndCopyGoals],
|
|
StatusAfterITE = StatusAfterThen
|
|
;
|
|
StatusBefore = status_unknown,
|
|
varset.lookup_name(!.VarSet, SVar, SVarName),
|
|
!:ElseMissingInits =
|
|
["!:" ++ SVarName | !.ElseMissingInits],
|
|
% We pretend the else part defines StateVar, since this is
|
|
% the right thing to do when the else part cannot succeed.
|
|
% If it can, we will generate an error message during
|
|
% mode analysis.
|
|
StatusAfterITE = StatusAfterThen
|
|
;
|
|
StatusBefore = status_known_ro(_, _, _),
|
|
% The update of !SVar in the then case was an error,
|
|
% for which we have already generated an error message.
|
|
% Because of that, this dummy value won't be used.
|
|
% XXX Returning StatusAfterThen would cause fewer cascading
|
|
% error messages, but are those messages useful or not?
|
|
StatusAfterITE = StatusBefore
|
|
;
|
|
( StatusBefore = status_known_updated(_, _)
|
|
; StatusBefore = status_unknown_updated(_)
|
|
),
|
|
% This can happen if LocKind = loc_inside_atomic_goal,
|
|
% but any reference to !:SVar in the then case should
|
|
% have just returned the new progvar for SVar.
|
|
unexpected($pred, "updated before (case 3)")
|
|
)
|
|
else
|
|
% Case 4.
|
|
VarAfterThen =
|
|
svar_get_current_progvar(LocKind, StatusAfterThen),
|
|
VarAfterElse =
|
|
svar_get_current_progvar(LocKind, StatusAfterElse),
|
|
!:ElseRenames = [VarAfterElse - VarAfterThen | !.ElseRenames],
|
|
StatusAfterITE = StatusAfterThen
|
|
)
|
|
)
|
|
else
|
|
% Cases 5-8.
|
|
( if StatusAfterThen = StatusAfterCond then
|
|
% Cases 5-6.
|
|
( if StatusAfterElse = StatusBefore then
|
|
% Case 5.
|
|
(
|
|
StatusBefore = status_known(VarBefore),
|
|
new_state_var_instance(SVar, name_middle, FinalVar,
|
|
!VarSet),
|
|
VarAfterCond =
|
|
svar_get_current_progvar(LocKind, StatusAfterCond),
|
|
|
|
make_copy_goal(VarAfterCond, FinalVar, NeckCopyGoal),
|
|
!:NeckCopyGoals = [NeckCopyGoal | !.NeckCopyGoals],
|
|
make_copy_goal(VarBefore, FinalVar, ElseCopyGoal),
|
|
!:ElseEndCopyGoals = [ElseCopyGoal | !.ElseEndCopyGoals],
|
|
StatusAfterITE = status_known(FinalVar)
|
|
;
|
|
StatusBefore = status_unknown,
|
|
varset.lookup_name(!.VarSet, SVar, SVarName),
|
|
!:ElseMissingInits =
|
|
["!:" ++ SVarName | !.ElseMissingInits],
|
|
% We pretend the else part defines StateVar, since this is
|
|
% the right thing to do when the else part cannot succeed.
|
|
% If it can, we will generate an error message during
|
|
% mode analysis.
|
|
new_state_var_instance(SVar, name_middle, FinalVar,
|
|
!VarSet),
|
|
VarAfterCond =
|
|
svar_get_current_progvar(LocKind, StatusAfterCond),
|
|
make_copy_goal(VarAfterCond, FinalVar, NeckCopyGoal),
|
|
!:NeckCopyGoals = [NeckCopyGoal | !.NeckCopyGoals],
|
|
StatusAfterITE = status_known(FinalVar)
|
|
;
|
|
StatusBefore = status_known_ro(_, _, _),
|
|
% The update of !SVar in the condition was an error,
|
|
% for which we have already generated an error message.
|
|
% Because of that, this dummy value won't be used.
|
|
% XXX Returning StatusAfterCond would cause fewer cascading
|
|
% error messages, but are those messages useful or not?
|
|
StatusAfterITE = StatusBefore
|
|
;
|
|
( StatusBefore = status_known_updated(_, _)
|
|
; StatusBefore = status_unknown_updated(_)
|
|
),
|
|
% This can happen if LocKind = loc_inside_atomic_goal,
|
|
% but any reference to !:SVar in the condition should
|
|
% have just returned the new progvar for SVar.
|
|
unexpected($pred, "updated before (case 5)")
|
|
)
|
|
else
|
|
% Case 6.
|
|
VarAfterCond =
|
|
svar_get_current_progvar(LocKind, StatusAfterCond),
|
|
VarAfterElse =
|
|
svar_get_current_progvar(LocKind, StatusAfterElse),
|
|
make_copy_goal(VarAfterCond, VarAfterElse, CopyGoal),
|
|
!:NeckCopyGoals = [CopyGoal | !.NeckCopyGoals],
|
|
StatusAfterITE = StatusAfterElse
|
|
)
|
|
else
|
|
% Cases 7-8.
|
|
( if StatusAfterElse = StatusBefore then
|
|
% Case 7.
|
|
(
|
|
StatusBefore = status_known(VarBefore),
|
|
VarAfterThen =
|
|
svar_get_current_progvar(LocKind, StatusAfterThen),
|
|
make_copy_goal(VarBefore, VarAfterThen, CopyGoal),
|
|
!:ElseEndCopyGoals = [CopyGoal | !.ElseEndCopyGoals],
|
|
StatusAfterITE = StatusAfterThen
|
|
;
|
|
StatusBefore = status_unknown,
|
|
varset.lookup_name(!.VarSet, SVar, SVarName),
|
|
!:ElseMissingInits =
|
|
["!:" ++ SVarName | !.ElseMissingInits],
|
|
% We pretend the else part defines StateVar, since this is
|
|
% the right thing to do when the else part cannot succeed.
|
|
% If it can, we will generate an error message during
|
|
% mode analysis.
|
|
StatusAfterITE = StatusAfterThen
|
|
;
|
|
StatusBefore = status_known_ro(_, _, _),
|
|
% The updates of !SVar in the condition and then cases
|
|
% were errors, for which we already generated messages.
|
|
% Because of that, this dummy value won't be used.
|
|
% XXX Returning StatusAfterThen would cause fewer cascading
|
|
% error messages, but are those messages useful or not?
|
|
StatusAfterITE = StatusBefore
|
|
;
|
|
( StatusBefore = status_known_updated(_, _)
|
|
; StatusBefore = status_unknown_updated(_)
|
|
),
|
|
% This can happen if LocKind = loc_inside_atomic_goal,
|
|
% but any reference to !:SVar in the condition and
|
|
% then case should have just returned the new progvar
|
|
% for SVar.
|
|
unexpected($pred, "updated before (case 7)")
|
|
)
|
|
else
|
|
% Case 8.
|
|
VarAfterThen =
|
|
svar_get_current_progvar(LocKind, StatusAfterThen),
|
|
VarAfterElse =
|
|
svar_get_current_progvar(LocKind, StatusAfterElse),
|
|
!:ElseRenames = [VarAfterElse - VarAfterThen | !.ElseRenames],
|
|
StatusAfterITE = StatusAfterThen
|
|
)
|
|
)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Handle atomic goals. Atomic goals are basically a disjunction between
|
|
% the main goal and the orelse goals.
|
|
%
|
|
|
|
:- type svar_outer_atomic_scope_info
|
|
---> svar_outer_atomic_scope_info(
|
|
soasi_state_var :: svar,
|
|
soasi_before_status :: svar_status,
|
|
soasi_after_status :: svar_status
|
|
)
|
|
; no_svar_outer_atomic_scope_info.
|
|
|
|
svar_start_outer_atomic_scope(Context, OuterStateVar, OuterDIVar, OuterUOVar,
|
|
OuterScopeInfo, !State, !VarSet, !Specs) :-
|
|
StatusMap0 = !.State ^ state_status_map,
|
|
( if map.remove(OuterStateVar, BeforeStatus, StatusMap0, StatusMap) then
|
|
!State ^ state_status_map := StatusMap,
|
|
(
|
|
BeforeStatus = status_unknown,
|
|
report_uninitialized_state_var(Context, !.VarSet, OuterStateVar,
|
|
!Specs),
|
|
new_state_var_instance(OuterStateVar, name_middle, OuterDIVar,
|
|
!VarSet),
|
|
new_state_var_instance(OuterStateVar, name_middle, OuterUOVar,
|
|
!VarSet),
|
|
OuterScopeInfo = svar_outer_atomic_scope_info(OuterStateVar,
|
|
BeforeStatus, BeforeStatus)
|
|
;
|
|
BeforeStatus = status_known_ro(OuterDIVar, RO_Construct,
|
|
RO_Context),
|
|
report_illegal_state_var_update(Context,
|
|
ro_construct_name(RO_Construct), RO_Context, !.VarSet,
|
|
OuterStateVar, !Specs),
|
|
new_state_var_instance(OuterStateVar, name_middle, OuterUOVar,
|
|
!VarSet),
|
|
OuterScopeInfo = svar_outer_atomic_scope_info(OuterStateVar,
|
|
BeforeStatus, BeforeStatus)
|
|
;
|
|
BeforeStatus = status_known(OuterDIVar),
|
|
new_state_var_instance(OuterStateVar, name_middle, OuterUOVar,
|
|
!VarSet),
|
|
AfterStatus = status_known(OuterUOVar),
|
|
OuterScopeInfo = svar_outer_atomic_scope_info(OuterStateVar,
|
|
BeforeStatus, AfterStatus)
|
|
;
|
|
( BeforeStatus = status_known_updated(_, _)
|
|
; BeforeStatus = status_unknown_updated(_)
|
|
),
|
|
% This status should exist in a status map only when we are in the
|
|
% middle of processing an atomic goal.
|
|
unexpected($pred, "status updated")
|
|
)
|
|
else
|
|
report_non_visible_state_var("", Context, !.VarSet, OuterStateVar,
|
|
!Specs),
|
|
new_state_var_instance(OuterStateVar, name_middle, OuterDIVar,
|
|
!VarSet),
|
|
new_state_var_instance(OuterStateVar, name_middle, OuterUOVar,
|
|
!VarSet),
|
|
OuterScopeInfo = no_svar_outer_atomic_scope_info
|
|
).
|
|
|
|
svar_finish_outer_atomic_scope(OuterScopeInfo, !State) :-
|
|
(
|
|
OuterScopeInfo = svar_outer_atomic_scope_info(OuterStateVar,
|
|
_BeforeStatus, AfterStatus),
|
|
StatusMap0 = !.State ^ state_status_map,
|
|
map.det_insert(OuterStateVar, AfterStatus, StatusMap0, StatusMap),
|
|
!State ^ state_status_map := StatusMap
|
|
;
|
|
OuterScopeInfo = no_svar_outer_atomic_scope_info
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type svar_inner_atomic_scope_info
|
|
---> svar_inner_atomic_scope_info(
|
|
siasi_state_var :: svar,
|
|
siasi_di_var :: prog_var,
|
|
siasi_state_before :: svar_state
|
|
).
|
|
|
|
svar_start_inner_atomic_scope(_Context, InnerStateVar, InnerScopeInfo,
|
|
!State, !VarSet, !Specs) :-
|
|
StateBefore = !.State,
|
|
new_state_var_instance(InnerStateVar, name_initial, InnerDIVar, !VarSet),
|
|
StatusMap0 = !.State ^ state_status_map,
|
|
map.set(InnerStateVar, status_known(InnerDIVar), StatusMap0, StatusMap),
|
|
!State ^ state_status_map := StatusMap,
|
|
InnerScopeInfo = svar_inner_atomic_scope_info(InnerStateVar, InnerDIVar,
|
|
StateBefore).
|
|
|
|
svar_finish_inner_atomic_scope(_Context, InnerScopeInfo,
|
|
InnerDIVar, InnerUOVar, !State, !VarSet, !Specs) :-
|
|
InnerScopeInfo = svar_inner_atomic_scope_info(InnerStateVar, InnerDIVar,
|
|
StateBefore),
|
|
StatusMap0 = !.State ^ state_status_map,
|
|
map.lookup(StatusMap0, InnerStateVar, Status),
|
|
(
|
|
Status = status_known(InnerUOVar)
|
|
;
|
|
( Status = status_unknown
|
|
; Status = status_unknown_updated(_)
|
|
; Status = status_known_ro(_, _, _)
|
|
; Status = status_known_updated(_, _)
|
|
),
|
|
unexpected($pred, "status != known")
|
|
),
|
|
!:State = StateBefore.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Look up prog_vars for a state_var.
|
|
%
|
|
|
|
substitute_state_var_mappings([], [], !VarSet, !State, !Specs).
|
|
substitute_state_var_mappings([Arg0 | Args0], [Arg | Args], !VarSet, !State,
|
|
!Specs) :-
|
|
substitute_state_var_mapping(Arg0, Arg, !VarSet, !State, !Specs),
|
|
substitute_state_var_mappings(Args0, Args, !VarSet, !State, !Specs).
|
|
|
|
substitute_state_var_mapping(Arg0, Arg, !VarSet, !State, !Specs) :-
|
|
( if Arg0 = functor(atom("!."), [variable(StateVar, _)], Context) then
|
|
lookup_dot_state_var(Context, StateVar, Var, !VarSet, !State, !Specs),
|
|
Arg = variable(Var, Context)
|
|
else if Arg0 = functor(atom("!:"), [variable(StateVar, _)], Context) then
|
|
lookup_colon_state_var(Context, StateVar, Var, !VarSet, !State,
|
|
!Specs),
|
|
Arg = variable(Var, Context)
|
|
else
|
|
Arg = Arg0
|
|
).
|
|
|
|
lookup_dot_state_var(Context, StateVar, Var, !VarSet, !State, !Specs) :-
|
|
StatusMap0 = !.State ^ state_status_map,
|
|
( if map.search(StatusMap0, StateVar, Status) then
|
|
(
|
|
Status = status_unknown,
|
|
report_uninitialized_state_var(Context, !.VarSet, StateVar,
|
|
!Specs),
|
|
% We make StateVar known to avoid duplicate reports.
|
|
new_state_var_instance(StateVar, name_middle, Var, !VarSet),
|
|
map.det_update(StateVar, status_known(Var),
|
|
StatusMap0, StatusMap),
|
|
!State ^ state_status_map := StatusMap
|
|
;
|
|
Status = status_unknown_updated(NewVar),
|
|
report_uninitialized_state_var(Context, !.VarSet, StateVar,
|
|
!Specs),
|
|
% We make StateVar known to avoid duplicate reports.
|
|
new_state_var_instance(StateVar, name_middle, Var, !VarSet),
|
|
map.det_update(StateVar, status_known_updated(Var, NewVar),
|
|
StatusMap0, StatusMap),
|
|
!State ^ state_status_map := StatusMap
|
|
;
|
|
( Status = status_known(Var)
|
|
; Status = status_known_ro(Var, _, _)
|
|
; Status = status_known_updated(Var, _)
|
|
)
|
|
)
|
|
else
|
|
report_non_visible_state_var(".", Context, !.VarSet, StateVar, !Specs),
|
|
Var = StateVar
|
|
).
|
|
|
|
lookup_colon_state_var(Context, StateVar, Var, !VarSet, !State, !Specs) :-
|
|
StatusMap0 = !.State ^ state_status_map,
|
|
( if map.search(StatusMap0, StateVar, Status) then
|
|
(
|
|
Status = status_unknown,
|
|
new_state_var_instance(StateVar, name_middle, Var, !VarSet),
|
|
map.det_update(StateVar, status_unknown_updated(Var),
|
|
StatusMap0, StatusMap),
|
|
!State ^ state_status_map := StatusMap
|
|
;
|
|
Status = status_known(OldVar),
|
|
new_state_var_instance(StateVar, name_middle, Var, !VarSet),
|
|
map.det_update(StateVar, status_known_updated(OldVar, Var),
|
|
StatusMap0, StatusMap),
|
|
!State ^ state_status_map := StatusMap
|
|
;
|
|
Status = status_known_ro(OldVar, RO_Construct, RO_Context),
|
|
(
|
|
RO_Construct = roc_lambda,
|
|
RO_ConstructName = "lambda expression"
|
|
),
|
|
report_illegal_state_var_update(Context, RO_ConstructName,
|
|
RO_Context, !.VarSet, StateVar, !Specs),
|
|
% We remove the readonly notation to avoid duplicate reports.
|
|
new_state_var_instance(StateVar, name_middle, Var, !VarSet),
|
|
map.det_update(StateVar, status_known_updated(OldVar, Var),
|
|
StatusMap0, StatusMap),
|
|
!State ^ state_status_map := StatusMap
|
|
;
|
|
Status = status_known_updated(_OldVar, Var)
|
|
;
|
|
Status = status_unknown_updated(Var)
|
|
)
|
|
else
|
|
report_non_visible_state_var(":", Context, !.VarSet, StateVar, !Specs),
|
|
% We could make StateVar known to avoid duplicate reports.
|
|
% new_state_var_instance(StateVar, name_initial, Var, !VarSet),
|
|
% map.det_insert(StateVar, status_known_updated(Var, Var),
|
|
% StatusMap0, StatusMap),
|
|
% !State ^ state_status_map := StatusMap
|
|
Var = StateVar
|
|
).
|
|
|
|
% Look up the prog_var representing the current state of the state_var
|
|
% whose status is given as the second argument.
|
|
%
|
|
:- func svar_get_current_progvar(loc_kind, svar_status) = prog_var.
|
|
|
|
svar_get_current_progvar(LocKind, Status) = ProgVar :-
|
|
(
|
|
LocKind = loc_whole_goal,
|
|
(
|
|
Status = status_known(ProgVar)
|
|
;
|
|
( Status = status_known_ro(_, _, _)
|
|
; Status = status_known_updated(_, _)
|
|
; Status = status_unknown
|
|
; Status = status_unknown_updated(_)
|
|
),
|
|
unexpected($pred, "Status not known")
|
|
)
|
|
;
|
|
LocKind = loc_inside_atomic_goal,
|
|
(
|
|
Status = status_known(ProgVar)
|
|
;
|
|
Status = status_known_updated(_, ProgVar)
|
|
;
|
|
Status = status_unknown_updated(ProgVar)
|
|
;
|
|
( Status = status_known_ro(_, _, _)
|
|
; Status = status_unknown
|
|
),
|
|
unexpected($pred, "Status not known or updated")
|
|
)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Code to handle the flattening of conjunctions. We need to be careful when we
|
|
% do so, since the goal we flatten could have a goal id, which would mean that
|
|
% the svar_store could have a delayed remapping for that goal_id. Just
|
|
% flattening the goal would remove the goal_info containing the goal_id from
|
|
% the HLDS, and the delayed renaming would not get done.
|
|
%
|
|
% We therefore make sure that when we flatten such a goal, we ensure that
|
|
% its subgoals all have goal_ids (creating new ones if needed), and that
|
|
% the delayed renaming that now won't get done on the conjunction as a whole
|
|
% *will* get done on each conjunct.
|
|
%
|
|
|
|
svar_flatten_conj(Context, Goals, Goal, !Store) :-
|
|
list.map_foldl(svar_goal_to_conj_list, Goals, GoalConjuncts, !Store),
|
|
list.condense(GoalConjuncts, Conjuncts),
|
|
GoalExpr = conj(plain_conj, Conjuncts),
|
|
goal_info_init(Context, GoalInfo),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo).
|
|
|
|
svar_goal_to_conj_list(Goal, Conjuncts, !Store) :-
|
|
% The code here is the same as in svar_goal_to_conj_list_internal,
|
|
% modulo the differences in the argument list.
|
|
Goal = hlds_goal(GoalExpr, GoalInfo),
|
|
( if GoalExpr = conj(plain_conj, Conjuncts0) then
|
|
!.Store = svar_store(NextGoalId0, DelayedRenamingMap0, Specs),
|
|
GoalId = goal_info_get_goal_id(GoalInfo),
|
|
( if map.search(DelayedRenamingMap0, GoalId, GoalDelayedRenaming) then
|
|
list.map_foldl2(
|
|
add_conjunct_delayed_renames(GoalDelayedRenaming),
|
|
Conjuncts0, Conjuncts, NextGoalId0, NextGoalId,
|
|
DelayedRenamingMap0, DelayedRenamingMap),
|
|
!:Store = svar_store(NextGoalId, DelayedRenamingMap, Specs)
|
|
else
|
|
Conjuncts = Conjuncts0
|
|
)
|
|
else
|
|
Conjuncts = [Goal]
|
|
).
|
|
|
|
:- pred svar_goal_to_conj_list_internal(hlds_goal::in, list(hlds_goal)::out,
|
|
counter::in, counter::out,
|
|
incremental_rename_map::in, incremental_rename_map::out) is det.
|
|
|
|
svar_goal_to_conj_list_internal(Goal, Conjuncts,
|
|
!NextGoalId, !DelayedRenamingMap) :-
|
|
% The code here is the same as in svar_goal_to_conj_list,
|
|
% modulo the differences in the argument list.
|
|
Goal = hlds_goal(GoalExpr, GoalInfo),
|
|
( if GoalExpr = conj(plain_conj, Conjuncts0) then
|
|
GoalId = goal_info_get_goal_id(GoalInfo),
|
|
( if map.search(!.DelayedRenamingMap, GoalId, GoalDelayedRenaming) then
|
|
list.map_foldl2(
|
|
add_conjunct_delayed_renames(GoalDelayedRenaming),
|
|
Conjuncts0, Conjuncts, !NextGoalId, !DelayedRenamingMap)
|
|
else
|
|
Conjuncts = Conjuncts0
|
|
)
|
|
else
|
|
Conjuncts = [Goal]
|
|
).
|
|
|
|
:- pred add_conjunct_delayed_renames(assoc_list(prog_var, prog_var)::in,
|
|
hlds_goal::in, hlds_goal::out, counter::in, counter::out,
|
|
incremental_rename_map::in, incremental_rename_map::out) is det.
|
|
|
|
add_conjunct_delayed_renames(DelayedRenamingToAdd, Goal0, Goal,
|
|
!NextGoalId, !DelayedRenamingMap) :-
|
|
Goal0 = hlds_goal(GoalExpr, GoalInfo0),
|
|
GoalId0 = goal_info_get_goal_id(GoalInfo0),
|
|
( if map.search(!.DelayedRenamingMap, GoalId0, DelayedRenaming0) then
|
|
% The goal id must be valid.
|
|
DelayedRenaming = DelayedRenamingToAdd ++ DelayedRenaming0,
|
|
map.det_update(GoalId0, DelayedRenaming, !DelayedRenamingMap),
|
|
Goal = Goal0
|
|
else
|
|
% The goal id must be invalid, since the only thing that attaches goal
|
|
% ids to goals at this stage of the compilation process is this module,
|
|
% and it attaches goal_ids to goals only if it also puts them the
|
|
% delayed renaming map.
|
|
counter.allocate(GoalIdNum, !NextGoalId),
|
|
GoalId = goal_id(GoalIdNum),
|
|
goal_info_set_goal_id(GoalId, GoalInfo0, GoalInfo),
|
|
map.det_insert(GoalId, DelayedRenamingToAdd, !DelayedRenamingMap),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% A post-pass to delete unneeded copy unifications. Such unifications
|
|
% can hide singleton variable problems.
|
|
%
|
|
|
|
:- pred delete_unneeded_copy_goals(hlds_goal::in, hlds_goal::out,
|
|
set_of_progvar::in, set_of_progvar::out) is det.
|
|
|
|
delete_unneeded_copy_goals(Goal0, Goal, SeenAfter, SeenBefore) :-
|
|
Goal0 = hlds_goal(GoalExpr0, GoalInfo),
|
|
(
|
|
GoalExpr0 = unify(LHSVar, _, _, _, _),
|
|
goal_vars(Goal0, GoalVars0),
|
|
( if
|
|
goal_info_has_feature(GoalInfo, feature_state_var_copy),
|
|
not set_of_var.member(SeenAfter, LHSVar)
|
|
then
|
|
Goal = hlds_goal(true_goal_expr, GoalInfo),
|
|
SeenBefore = SeenAfter
|
|
else
|
|
set_of_var.union(GoalVars0, SeenAfter, SeenBefore),
|
|
Goal = Goal0
|
|
)
|
|
;
|
|
( GoalExpr0 = plain_call(_, _, _, _, _, _)
|
|
; GoalExpr0 = generic_call(_, _, _, _, _)
|
|
; GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _)
|
|
),
|
|
goal_vars(Goal0, GoalVars0),
|
|
set_of_var.union(GoalVars0, SeenAfter, SeenBefore),
|
|
Goal = Goal0
|
|
;
|
|
GoalExpr0 = conj(ConjKind, Conjuncts0),
|
|
% Processing Conjuncts0 without reversing it would lead to recursion
|
|
% as deep as Conjuncts0 is long. Since Conjuncts0 can be very long,
|
|
% we prefer to pay the price of reversing and unreversing the list
|
|
% to achieve tail recursion.
|
|
list.reverse(Conjuncts0, RevConjuncts0),
|
|
delete_unneeded_copy_goals_rev_conj(RevConjuncts0, RevConjuncts,
|
|
SeenAfter, SeenBefore),
|
|
list.reverse(RevConjuncts, Conjuncts),
|
|
GoalExpr = conj(ConjKind, Conjuncts),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo)
|
|
;
|
|
GoalExpr0 = disj(Disjuncts0),
|
|
delete_unneeded_copy_goals_disj(Disjuncts0, Disjuncts,
|
|
SeenAfter, SeenBefores),
|
|
GoalExpr = disj(Disjuncts),
|
|
set_of_var.union_list(SeenBefores, SeenBefore),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo)
|
|
;
|
|
GoalExpr0 = switch(SwitchVar, CanFail, Cases0),
|
|
% Switches should not exist at this point in the compilation process,
|
|
% but it is simple enough to prepare here for the eventuality that
|
|
% this may change in the future.
|
|
delete_unneeded_copy_goals_switch(Cases0, Cases,
|
|
SeenAfter, SeenBefores),
|
|
GoalExpr = switch(SwitchVar, CanFail, Cases),
|
|
set_of_var.union_list(SeenBefores, SeenBefore0),
|
|
set_of_var.insert(SwitchVar, SeenBefore0, SeenBefore),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo)
|
|
;
|
|
GoalExpr0 = if_then_else(ITEVars, Cond0, Then0, Else0),
|
|
delete_unneeded_copy_goals(Else0, Else, SeenAfter, SeenBeforeElse),
|
|
delete_unneeded_copy_goals(Then0, Then, SeenAfter, SeenAfterThen),
|
|
delete_unneeded_copy_goals(Cond0, Cond, SeenAfterThen, SeenBeforeCond),
|
|
GoalExpr = if_then_else(ITEVars, Cond, Then, Else),
|
|
set_of_var.union(SeenBeforeCond, SeenBeforeElse, SeenBefore0),
|
|
set_of_var.insert_list(ITEVars, SeenBefore0, SeenBefore),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo)
|
|
;
|
|
GoalExpr0 = negation(SubGoal0),
|
|
delete_unneeded_copy_goals(SubGoal0, SubGoal, SeenAfter, SeenBefore),
|
|
GoalExpr = negation(SubGoal),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo)
|
|
;
|
|
GoalExpr0 = scope(Reason, SubGoal0),
|
|
(
|
|
Reason = from_ground_term(TermVar, _Kind),
|
|
% There won't be any feature_state_var_copy goals inside SubGoal0.
|
|
SubGoal = SubGoal0,
|
|
% None of the variables in SubGoal can occur in the rest of the
|
|
% procedure body, with the exception of TermVar.
|
|
set_of_var.insert(TermVar, SeenAfter, SeenBefore)
|
|
;
|
|
( Reason = require_complete_switch(ScopeVar)
|
|
; Reason = require_switch_arms_detism(ScopeVar, _Detism)
|
|
),
|
|
delete_unneeded_copy_goals(SubGoal0, SubGoal,
|
|
SeenAfter, SeenBefore0),
|
|
set_of_var.insert(ScopeVar, SeenBefore0, SeenBefore)
|
|
;
|
|
Reason = loop_control(LCVar, LCSVar, _UseParentStack),
|
|
delete_unneeded_copy_goals(SubGoal0, SubGoal,
|
|
SeenAfter, SeenBefore0),
|
|
set_of_var.insert_list([LCVar, LCSVar], SeenBefore0, SeenBefore)
|
|
;
|
|
( Reason = exist_quant(ScopeVars)
|
|
; Reason = promise_solutions(ScopeVars, _PromiseKind)
|
|
; Reason = trace_goal(_Comp, _Run, _MaybeIO, _Mutables, ScopeVars)
|
|
),
|
|
delete_unneeded_copy_goals(SubGoal0, SubGoal,
|
|
SeenAfter, SeenBefore0),
|
|
set_of_var.insert_list(ScopeVars, SeenBefore0, SeenBefore)
|
|
;
|
|
( Reason = disable_warnings(_, _)
|
|
; Reason = promise_purity(_)
|
|
; Reason = require_detism(_)
|
|
; Reason = commit(_)
|
|
; Reason = barrier(_)
|
|
),
|
|
delete_unneeded_copy_goals(SubGoal0, SubGoal,
|
|
SeenAfter, SeenBefore)
|
|
),
|
|
GoalExpr = scope(Reason, SubGoal),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo)
|
|
;
|
|
GoalExpr0 = shorthand(ShortHand0),
|
|
(
|
|
ShortHand0 = atomic_goal(AtomicType,
|
|
atomic_interface_vars(OuterInitVar, OuterFinalVar),
|
|
atomic_interface_vars(InnerInitVar, InnerFinalVar),
|
|
MaybeOutputVars, MainGoal0, OrElseGoals0, OrElseInners),
|
|
expect(unify(OrElseInners, []), $pred, "OrElseInners != []"),
|
|
Disjuncts0 = [MainGoal0 | OrElseGoals0],
|
|
delete_unneeded_copy_goals_disj(Disjuncts0, Disjuncts,
|
|
SeenAfter, SeenBefores),
|
|
(
|
|
Disjuncts = [],
|
|
unexpected($pred, "Disjuncts = []")
|
|
;
|
|
Disjuncts = [MainGoal | OrElseGoals]
|
|
),
|
|
ShortHand = atomic_goal(AtomicType,
|
|
atomic_interface_vars(OuterInitVar, OuterFinalVar),
|
|
atomic_interface_vars(InnerInitVar, InnerFinalVar),
|
|
MaybeOutputVars, MainGoal, OrElseGoals, OrElseInners),
|
|
set_of_var.union_list(SeenBefores, SeenBefore0),
|
|
set_of_var.insert_list([OuterInitVar, OuterFinalVar,
|
|
InnerInitVar, InnerFinalVar], SeenBefore0, SeenBefore1),
|
|
(
|
|
MaybeOutputVars = no,
|
|
SeenBefore = SeenBefore1
|
|
;
|
|
MaybeOutputVars = yes(OutputVars),
|
|
set_of_var.insert_list(OutputVars, SeenBefore1, SeenBefore)
|
|
)
|
|
;
|
|
ShortHand0 = try_goal(MaybeIOStateVars, ResultVar, SubGoal0),
|
|
delete_unneeded_copy_goals(SubGoal0, SubGoal,
|
|
SeenAfter, SeenBefore0),
|
|
set_of_var.insert(ResultVar, SeenBefore0, SeenBefore1),
|
|
(
|
|
MaybeIOStateVars = no,
|
|
SeenBefore = SeenBefore1
|
|
;
|
|
MaybeIOStateVars = yes(try_io_state_vars(InitVar, FinalVar)),
|
|
set_of_var.insert(InitVar, SeenBefore1, SeenBefore2),
|
|
set_of_var.insert(FinalVar, SeenBefore2, SeenBefore)
|
|
),
|
|
ShortHand = try_goal(MaybeIOStateVars, ResultVar, SubGoal)
|
|
;
|
|
ShortHand0 = bi_implication(LeftGoal0, RightGoal0),
|
|
delete_unneeded_copy_goals(LeftGoal0, LeftGoal,
|
|
SeenAfter, SeenBeforeLeft),
|
|
delete_unneeded_copy_goals(RightGoal0, RightGoal,
|
|
SeenAfter, SeenBeforeRight),
|
|
set_of_var.union(SeenBeforeLeft, SeenBeforeRight, SeenBefore),
|
|
ShortHand = bi_implication(LeftGoal, RightGoal)
|
|
),
|
|
GoalExpr = shorthand(ShortHand),
|
|
Goal = hlds_goal(GoalExpr, GoalInfo)
|
|
).
|
|
|
|
:- pred delete_unneeded_copy_goals_rev_conj(
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
set_of_progvar::in, set_of_progvar::out) is det.
|
|
|
|
delete_unneeded_copy_goals_rev_conj([], [], SeenAfter, SeenBefore) :-
|
|
SeenBefore = SeenAfter.
|
|
delete_unneeded_copy_goals_rev_conj(
|
|
[RevConjunct0 | RevConjuncts0], [RevConjunct | RevConjuncts],
|
|
SeenAfter, SeenBefore) :-
|
|
delete_unneeded_copy_goals(RevConjunct0, RevConjunct,
|
|
SeenAfter, SeenBetween),
|
|
delete_unneeded_copy_goals_rev_conj(RevConjuncts0, RevConjuncts,
|
|
SeenBetween, SeenBefore).
|
|
|
|
:- pred delete_unneeded_copy_goals_disj(
|
|
list(hlds_goal)::in, list(hlds_goal)::out,
|
|
set_of_progvar::in, list(set_of_progvar)::out) is det.
|
|
|
|
delete_unneeded_copy_goals_disj([], [], _, []).
|
|
delete_unneeded_copy_goals_disj(
|
|
[Disjunct0 | Disjuncts0], [Disjunct | Disjuncts],
|
|
SeenAfter, [SeenBefore | SeenBefores]) :-
|
|
delete_unneeded_copy_goals(Disjunct0, Disjunct, SeenAfter, SeenBefore),
|
|
delete_unneeded_copy_goals_disj(Disjuncts0, Disjuncts,
|
|
SeenAfter, SeenBefores).
|
|
|
|
:- pred delete_unneeded_copy_goals_switch(list(case)::in, list(case)::out,
|
|
set_of_progvar::in, list(set_of_progvar)::out) is det.
|
|
|
|
delete_unneeded_copy_goals_switch([], [], _, []).
|
|
delete_unneeded_copy_goals_switch([Case0 | Cases0], [Case | Cases],
|
|
SeenAfter, [SeenBefore | SeenBefores]) :-
|
|
Case0 = case(MainConsId, OtherConsIds, Goal0),
|
|
delete_unneeded_copy_goals(Goal0, Goal, SeenAfter, SeenBefore),
|
|
Case = case(MainConsId, OtherConsIds, Goal),
|
|
delete_unneeded_copy_goals_switch(Cases0, Cases, SeenAfter, SeenBefores).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Test for various kinds of errors.
|
|
%
|
|
|
|
illegal_state_var_func_result(pf_function, ArgTerms, StateVar, Context) :-
|
|
list.last(ArgTerms, LastArgTerm),
|
|
is_term_a_bang_state_pair(LastArgTerm, StateVar, Context).
|
|
|
|
is_term_a_bang_state_pair(ArgTerm, StateVar, Context) :-
|
|
ArgTerm = functor(atom("!"), [variable(StateVar, Context)], _).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%
|
|
% Report various kinds of errors.
|
|
%
|
|
|
|
report_illegal_state_var_update(Context, RO_Construct, RO_Context, VarSet,
|
|
StateVar, !Specs) :-
|
|
Name = varset.lookup_name(VarSet, StateVar),
|
|
Pieces1 = [words("Error: cannot use"), fixed("!:" ++ Name),
|
|
words("here due to the surrounding"), words(RO_Construct), suffix(";"),
|
|
words("you may only refer to"), fixed("!." ++ Name), suffix("."), nl],
|
|
Msg1 = simplest_msg(Context, Pieces1),
|
|
Pieces2 = [words("Here is the surrounding context that makes"),
|
|
words("state variable"), fixed(Name), words("readonly."), nl],
|
|
Msg2 = simplest_msg(RO_Context, Pieces2),
|
|
Spec = error_spec($pred, severity_error, phase_parse_tree_to_hlds,
|
|
[Msg1, Msg2]),
|
|
!:Specs = [Spec | !.Specs].
|
|
|
|
:- func ro_construct_name(readonly_context_kind) = string.
|
|
|
|
ro_construct_name(roc_lambda) = "lambda expression".
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
report_illegal_func_svar_result(Context, VarSet, StateVar, !Specs) :-
|
|
Name = varset.lookup_name(VarSet, StateVar),
|
|
% While having !.Var appear as a function argument is quite ordinary,
|
|
% having it appear as a function *result* is not. We therefore do not
|
|
% suggest it as a likely correction.
|
|
Pieces = [words("Error:"), fixed("!" ++ Name),
|
|
words("cannot be a function result."), nl,
|
|
words("You probably meant"), fixed("!:" ++ Name), suffix("."), nl],
|
|
Spec = simplest_spec($pred, severity_error, phase_parse_tree_to_hlds,
|
|
Context, Pieces),
|
|
!:Specs = [Spec | !.Specs].
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
report_illegal_bang_svar_lambda_arg(Context, VarSet, StateVar, !Specs) :-
|
|
Name = varset.lookup_name(VarSet, StateVar),
|
|
Pieces = [words("Error:"), fixed("!" ++ Name),
|
|
words("cannot be a lambda argument."), nl,
|
|
words("Perhaps you meant"), fixed("!." ++ Name),
|
|
words("or"), fixed("!:" ++ Name), suffix("."), nl],
|
|
Spec = simplest_spec($pred, severity_error, phase_parse_tree_to_hlds,
|
|
Context, Pieces),
|
|
!:Specs = [Spec | !.Specs].
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred report_non_visible_state_var(string::in, prog_context::in,
|
|
prog_varset::in, svar::in, list(error_spec)::in, list(error_spec)::out)
|
|
is det.
|
|
|
|
report_non_visible_state_var(DorC, Context, VarSet, StateVar, !Specs) :-
|
|
Name = varset.lookup_name(VarSet, StateVar),
|
|
Pieces = [words("Error: state variable"), fixed("!" ++ DorC ++ Name),
|
|
words("is not visible in this context."), nl],
|
|
Spec = simplest_spec($pred, severity_error, phase_parse_tree_to_hlds,
|
|
Context, Pieces),
|
|
!:Specs = [Spec | !.Specs].
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred report_uninitialized_state_var(prog_context::in, prog_varset::in,
|
|
svar::in, list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
report_uninitialized_state_var(Context, VarSet, StateVar, !Specs) :-
|
|
Name = varset.lookup_name(VarSet, StateVar),
|
|
Pieces = [words("Warning: reference to uninitialized state variable"),
|
|
fixed("!." ++ Name), suffix("."), nl],
|
|
Spec = simplest_spec($pred, severity_warning, phase_parse_tree_to_hlds,
|
|
Context, Pieces),
|
|
!:Specs = [Spec | !.Specs].
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred report_repeated_head_state_var(prog_context::in, prog_varset::in,
|
|
svar::in, list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
report_repeated_head_state_var(Context, VarSet, StateVar, !Specs) :-
|
|
Name = varset.lookup_name(VarSet, StateVar),
|
|
Pieces = [words("Warning: clause head introduces"),
|
|
words("state variable"), fixed(Name), words("more than once."), nl],
|
|
Spec = simplest_spec($pred, severity_error, phase_parse_tree_to_hlds,
|
|
Context, Pieces),
|
|
!:Specs = [Spec | !.Specs].
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred report_state_var_shadow(prog_context::in, prog_varset::in,
|
|
svar::in, list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
report_state_var_shadow(Context, VarSet, StateVar, !Specs) :-
|
|
Name = varset.lookup_name(VarSet, StateVar),
|
|
Pieces = [words("Warning: new state variable"), fixed(Name),
|
|
words("shadows old one."), nl],
|
|
Spec = conditional_spec($pred, warn_state_var_shadowing, yes,
|
|
severity_warning, phase_parse_tree_to_hlds,
|
|
[simplest_msg(Context, Pieces)]),
|
|
!:Specs = [Spec | !.Specs].
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred report_missing_inits_in_ite(prog_context::in, list(string)::in,
|
|
string::in, string::in, list(error_spec)::in, list(error_spec)::out)
|
|
is det.
|
|
|
|
report_missing_inits_in_ite(Context, NextStateVars,
|
|
WhenMissing, WhenNotMissing, !Specs) :-
|
|
Pieces = [words("When the condition"), words(WhenNotMissing), suffix(","),
|
|
words("the if-then-else defines")] ++
|
|
list_to_pieces(NextStateVars) ++ [suffix(","),
|
|
words("but when the condition"), words(WhenMissing), suffix(","),
|
|
words("it does not."), nl],
|
|
Spec = simplest_spec($pred, severity_informational,
|
|
phase_parse_tree_to_hlds, Context, Pieces),
|
|
!:Specs = [Spec | !.Specs].
|
|
|
|
:- pred report_missing_inits_in_disjunct(prog_context::in, list(string)::in,
|
|
list(error_spec)::in, list(error_spec)::out) is det.
|
|
|
|
report_missing_inits_in_disjunct(Context, NextStateVars, !Specs) :-
|
|
Pieces = [words("Other disjuncts define")] ++
|
|
list_to_pieces(NextStateVars) ++ [suffix(","),
|
|
words("but not this one."), nl],
|
|
Spec = simplest_spec($pred, severity_informational,
|
|
phase_parse_tree_to_hlds, Context, Pieces),
|
|
!:Specs = [Spec | !.Specs].
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
report_svar_unify_error(Context, StateVar, !VarSet, !State, !Specs) :-
|
|
Name = varset.lookup_name(!.VarSet, StateVar),
|
|
Pieces = [words("Error:"), fixed("!" ++ Name),
|
|
words("cannot appear as a unification argument."), nl,
|
|
words("You probably meant"), fixed("!." ++ Name),
|
|
words("or"), fixed("!:" ++ Name), suffix(".")],
|
|
Spec = simplest_spec($pred, severity_error, phase_parse_tree_to_hlds,
|
|
Context, Pieces),
|
|
!:Specs = [Spec | !.Specs],
|
|
!.State = svar_state(StatusMap0),
|
|
% If StateVar was not known before, then this is the first occurrence
|
|
% of this state variable, and the user almost certainly intended it
|
|
% to define its initial value. Any messages from later goals complaining
|
|
% about the variable not being defined there would only be a distraction.
|
|
%
|
|
% Adding this dummy entry to the state, means we cannot generate valid
|
|
% HLDS goals, but the error reported just above ensures that we will
|
|
% throw away the HLDS goals we generate, so this is ok.
|
|
( if
|
|
map.search(StatusMap0, StateVar, OldStatus),
|
|
OldStatus \= status_unknown
|
|
then
|
|
% The state variable is already known.
|
|
true
|
|
else
|
|
new_state_var_instance(StateVar, name_initial, Var, !VarSet),
|
|
Status = status_known(Var),
|
|
map.set(StateVar, Status, StatusMap0, StatusMap),
|
|
!:State = svar_state(StatusMap)
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pred severity_is_error(globals::in, error_spec::in) is semidet.
|
|
|
|
severity_is_error(Globals, Spec) :-
|
|
actual_spec_severity(Globals, Spec) = yes(actual_severity_error).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module hlds.make_hlds.state_var.
|
|
%-----------------------------------------------------------------------------%
|