diff --git a/compiler/add_clause.m b/compiler/add_clause.m index e6d746758..9b3f0a086 100644 --- a/compiler/add_clause.m +++ b/compiler/add_clause.m @@ -75,6 +75,7 @@ :- import_module hlds.make_hlds.state_var. :- import_module hlds.make_hlds.superhomogeneous. :- import_module hlds.make_hlds.superhomogeneous_util. +:- import_module hlds.make_hlds.unravel_info. :- import_module hlds.make_hlds_error. :- import_module hlds.pre_quantification. :- import_module hlds.pred_info_types. diff --git a/compiler/delete_copy_goals.m b/compiler/delete_copy_goals.m new file mode 100644 index 000000000..b969c6af7 --- /dev/null +++ b/compiler/delete_copy_goals.m @@ -0,0 +1,323 @@ +%---------------------------------------------------------------------------% +% vim: ft=mercury ts=4 sw=4 et +%---------------------------------------------------------------------------% +% Copyright (C) 2005-2011 The University of Melbourne. +% Copyright (C) 2014-2016, 2018-2026 The Mercury team. +% This file may only be copied under the terms of the GNU General +% Public License - see the file COPYING in the Mercury distribution. +%---------------------------------------------------------------------------% +% +% File: delete_copy_goals.m. +% +% This module contains pass that executes after the transformation of +% clause bodies to superhomogeneous form, which also expands out references +% to state varuables. This post-pass deletes unneeded copy unifications +% that the previous pass could leave in its output. Such unifications +% can hide singleton variable problems. +% +%---------------------------------------------------------------------------% + +:- module hlds.make_hlds.delete_copy_goals. +:- interface. + +:- import_module hlds.hlds_goal. + + % Suppose we have a goal such as this: + % + % ( if p(..., !.S, !:S) then + % + % else + % + % ) + % + % 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 + % , STATE_VARIABLE_S_7 = STATE_VARIABLE_S_6 + % else + % , 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 HeadUnificationsGoal 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 in our caller will put the unification of the state + % var instances representing !.S and !:S *after* HeadUnificationsGoal. + % 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. + % +:- pred delete_unneeded_copy_goals_in_clause(hlds_goal::in, + hlds_goal::in, hlds_goal::out) is det. + +%---------------------------------------------------------------------------% +%---------------------------------------------------------------------------% + +:- implementation. + +:- import_module hlds.goal_vars. +:- import_module hlds.hlds_markers. +:- import_module hlds.make_goal. +:- import_module mdbcomp. +:- import_module mdbcomp.sym_name. +:- import_module parse_tree. +:- import_module parse_tree.prog_data. +:- import_module parse_tree.set_of_var. + +:- import_module list. +:- import_module maybe. +:- import_module require. +:- import_module term. + +%---------------------------------------------------------------------------% + +delete_unneeded_copy_goals_in_clause(HeadUnificationsGoal, Goal0, Goal) :- + vars_in_goal(HeadUnificationsGoal, HeadUnificationsGoalVars), + SeenLater0 = HeadUnificationsGoalVars, + delete_unneeded_copy_goals(Goal0, Goal, SeenLater0, _SeenLater). + +:- 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, _, _, _, _), + vars_in_goal(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(_, _, _, _, _, _, _) + ), + vars_in_goal(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). + +%---------------------------------------------------------------------------% +:- end_module hlds.make_hlds.delete_copy_goals. +%---------------------------------------------------------------------------% diff --git a/compiler/field_access.m b/compiler/field_access.m index 4b2fe2801..4545b49c9 100644 --- a/compiler/field_access.m +++ b/compiler/field_access.m @@ -18,6 +18,7 @@ :- import_module hlds.hlds_goal. :- import_module hlds.make_hlds.state_var. +:- import_module hlds.make_hlds.unravel_info. :- import_module mdbcomp. :- import_module mdbcomp.sym_name. :- import_module parse_tree. diff --git a/compiler/goal_expr_to_goal.m b/compiler/goal_expr_to_goal.m index aad87e350..3332f40e2 100644 --- a/compiler/goal_expr_to_goal.m +++ b/compiler/goal_expr_to_goal.m @@ -12,6 +12,7 @@ :- import_module hlds.hlds_goal. :- import_module hlds.make_hlds.state_var. +:- import_module hlds.make_hlds.unravel_info. :- import_module parse_tree. :- import_module parse_tree.prog_data. :- import_module parse_tree.prog_item. @@ -238,15 +239,18 @@ transform_parse_tree_goal_to_hlds_unify(LocKind, Renaming, Goal, HLDSGoal, % It is an error for the left or right hand side of a unification % to be !A (although it may be !.A or !:A). ( if TermA = functor(atom("!"), [variable(StateVarA, _)], _) then - report_svar_unify_error(Context, StateVarA, !SVarState, !UrInfo), + report_svar_unify_error(Context, StateVarA, !UrInfo), + make_svar_magically_known(StateVarA, !SVarState, !UrInfo), ( if TermB = functor(atom("!"), [variable(StateVarB, _)], _) then - report_svar_unify_error(Context, StateVarB, !SVarState, !UrInfo) + report_svar_unify_error(Context, StateVarB, !UrInfo), + make_svar_magically_known(StateVarB, !SVarState, !UrInfo) else true ), HLDSGoal = true_goal_with_context(Context) else if TermB = functor(atom("!"), [variable(StateVarB, _)], _) then - report_svar_unify_error(Context, StateVarB, !SVarState, !UrInfo), + report_svar_unify_error(Context, StateVarB, !UrInfo), + make_svar_magically_known(StateVarB, !SVarState, !UrInfo), HLDSGoal = true_goal_with_context(Context) else unravel_unification(TermA, TermB, Context, umc_explicit, [], diff --git a/compiler/instance_method_clauses.m b/compiler/instance_method_clauses.m index d90013151..6f83b854e 100644 --- a/compiler/instance_method_clauses.m +++ b/compiler/instance_method_clauses.m @@ -49,6 +49,7 @@ :- import_module hlds.instmap. :- import_module hlds.make_hlds.add_clause. :- import_module hlds.make_hlds.state_var. +:- import_module hlds.make_hlds.unravel_info. :- import_module hlds.pred_proc_id. :- import_module mdbcomp.sym_name. :- import_module parse_tree.maybe_error. diff --git a/compiler/make_hlds.m b/compiler/make_hlds.m index 8b94465d5..da9dd2f0b 100644 --- a/compiler/make_hlds.m +++ b/compiler/make_hlds.m @@ -41,6 +41,7 @@ :- include_module make_hlds_types. :- include_module qual_info. :- include_module state_var. +:- include_module unravel_info. :- implementation. @@ -60,6 +61,7 @@ :- include_module add_solver. :- include_module add_type. :- include_module check_field_access_functions. +:- include_module delete_copy_goals. :- include_module field_access. :- include_module goal_expr_to_goal. :- include_module make_hlds_separate_items. diff --git a/compiler/notes/compiler_design.html b/compiler/notes/compiler_design.html index 92ab911b3..18eb4258a 100644 --- a/compiler/notes/compiler_design.html +++ b/compiler/notes/compiler_design.html @@ -757,8 +757,14 @@ with the help of superhomogeneous_lambda.m (which handles the conversion of lambda expressions) and superhomogeneous_util.m (which provides utility predicates).
  • +unravel_info.m defines the unravel_info type, +which defines the state of the conversion into superhomogeneous form. +
  • state_var.m expands away state variable syntax.
  • +delete_copy_goals.m performs a post-pass +that cleans up the output of state_var.m. +
  • field_access.m expands away field access syntax.
  • check_field_access_functions.m checks diff --git a/compiler/state_var.m b/compiler/state_var.m index 461b2fecc..d3002ffef 100644 --- a/compiler/state_var.m +++ b/compiler/state_var.m @@ -21,8 +21,7 @@ :- import_module hlds.hlds_module. :- import_module hlds.make_hlds.goal_expr_to_goal. :- import_module hlds.make_hlds.qual_info. -:- import_module mdbcomp. -:- import_module mdbcomp.prim_data. +:- import_module hlds.make_hlds.unravel_info. :- import_module parse_tree. :- import_module parse_tree.error_spec. :- import_module parse_tree.prog_data. @@ -30,88 +29,6 @@ :- import_module list. :- import_module map. -:- import_module one_or_more. - -%---------------------------------------------------------------------------% - - % This type describes the state of the code that converts goals - % from their parse tree form to their HLDS form. Almost all the code - % in all the modules of the make_hlds package that handle goals - % pass around values of this type as effectively global state, - % with persistent updates. (The state of the state var transformation - % itself is threaded through that code in a different manner; - % see the definition of the svar_state type below.) - % - % With one exception, all of the fields are writeable. -:- type unravel_info - ---> unravel_info( - % The module_info, which we use for several purposes. - % Most uses are readonly, including getting the globals - % for option lookup, and the module name for creating - % debug output streams. - % - % The only situation in which we update the module_info field - % is when processing disable_warning scopes that disable - % the warning for occurs check violations. In that case, - % we set the option controlling that warning to "no" - % while processing the goal in the scope, and reset it - % afterwards. Such scopes are rare enough that storing the - % value of that option as a separate field in this structure - % would not be worthwhile. - ui_module_info :: module_info, - - % The value of the from_ground_term_threshold option. - % This field duplicates the value stored in the globals - % structure, but it is needed often enough that a separate - % fast-access copy is worthwhile. - % This field is read-only. - ui_fgt_threshold :: int, - - % The store where we record information about what entities - % imported from other modules are used. We use that info - % to generate warnings about unused imports. - ui_qual_info :: qual_info, - - % The varset of the clause whose goal we are converting. - % New instances of state variables are allocated from here. - ui_varset :: prog_varset, - - % The part of the state of the state var transformation - % that is updated persistently (meaning, that once we create - % a new version, we don't go back to look at previous - % versions.) - ui_state_var_store :: svar_store, - - % The errors and warnings that we definitely want to print. - % (The svar_store also contains warn_specs, but we print those - % only as hints *if and when* we later find certain other kinds - % of errors.) - ui_err_specs :: list(err_spec), - ui_warn_specs :: list(warn_spec) - ). - -%---------------------------------------------------------------------------% - -:- pred create_new_unravel_var(prog_var::out, - unravel_info::in, unravel_info::out) is det. - -:- pred create_new_named_unravel_var(string::in, prog_var::out, - unravel_info::in, unravel_info::out) is det. - -:- pred record_unravel_found_syntax_error( - unravel_info::in, unravel_info::out) is det. - -:- pred add_unravel_err(err_spec::in, - unravel_info::in, unravel_info::out) is det. -:- pred add_unravel_errs(list(err_spec)::in, - unravel_info::in, unravel_info::out) is det. -:- pred add_unravel_oom_errs(one_or_more(err_spec)::in, - unravel_info::in, unravel_info::out) is det. - -:- pred add_unravel_warn(warn_spec::in, - unravel_info::in, unravel_info::out) is det. -:- pred add_unravel_warns(list(warn_spec)::in, - unravel_info::in, unravel_info::out) is det. %---------------------------------------------------------------------------% @@ -154,6 +71,16 @@ :- pred is_prog_var_for_some_state_var(prog_varset::in, prog_var::in, string::out) is semidet. +%---------------------------------------------------------------------------% + + % When there is an illegal reference to as state variable + % (as in e.g !A = f), then we ensure that later references + % to e.g. !.A don't get avalance errors by pretending that + % the state variable A is already known. + % +:- pred make_svar_magically_known(svar::in, svar_state::in, svar_state::out, + unravel_info::in, unravel_info::out) is det. + %---------------------------------------------------------------------------% % Replace !X args with two args !.X, !:X in that order. @@ -361,71 +288,30 @@ :- pred svar_goal_to_conj_list(hlds_goal::in, list(hlds_goal)::out, unravel_info::in, unravel_info::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, svar::in, - unravel_info::in, unravel_info::out) is det. - -:- pred report_illegal_func_svar_result(prog_context::in, svar::in, - unravel_info::in, unravel_info::out) is det. -:- func report_illegal_func_svar_result_raw(prog_context, - prog_varset, svar) = err_spec. - -:- pred report_illegal_bang_svar_lambda_arg(prog_context::in, svar::in, - unravel_info::in, unravel_info::out) is det. -:- func report_illegal_bang_svar_lambda_arg_raw(prog_context, - prog_varset, svar) = err_spec. - -:- pred report_svar_unify_error(prog_context::in, svar::in, - svar_state::in, svar_state::out, - unravel_info::in, unravel_info::out) is det. - %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- implementation. -:- import_module hlds.goal_vars. :- import_module hlds.hlds_markers. :- import_module hlds.make_goal. -:- import_module hlds.mode_util. +:- import_module hlds.make_hlds.delete_copy_goals. :- import_module libs. :- import_module libs.globals. :- import_module libs.optimization_options. :- import_module libs.options. +:- import_module mdbcomp. :- import_module mdbcomp.goal_path. +:- import_module mdbcomp.prim_data. :- import_module mdbcomp.sym_name. :- 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 one_or_more. :- import_module one_or_more_map. :- import_module pair. :- import_module require. @@ -435,53 +321,6 @@ :- import_module uint. :- import_module varset. -%---------------------------------------------------------------------------% - -create_new_unravel_var(Var, !UrInfo) :- - VarSet0 = !.UrInfo ^ ui_varset, - varset.new_var(Var, VarSet0, VarSet), - !UrInfo ^ ui_varset := VarSet. - -create_new_named_unravel_var(Name, Var, !UrInfo) :- - VarSet0 = !.UrInfo ^ ui_varset, - varset.new_named_var(Name, Var, VarSet0, VarSet), - !UrInfo ^ ui_varset := VarSet. - -record_unravel_found_syntax_error(!UrInfo) :- - QualInfo0 = !.UrInfo ^ ui_qual_info, - qual_info_set_found_syntax_error(yes, QualInfo0, QualInfo), - !UrInfo ^ ui_qual_info := QualInfo. - -add_unravel_err(NewSpec, !UrInfo) :- - Specs0 = !.UrInfo ^ ui_err_specs, - Specs = [NewSpec | Specs0], - !UrInfo ^ ui_err_specs := Specs. - -add_unravel_errs(NewSpecs, !UrInfo) :- - ( - NewSpecs = [] - ; - NewSpecs = [_ | _], - Specs0 = !.UrInfo ^ ui_err_specs, - Specs = NewSpecs ++ Specs0, - !UrInfo ^ ui_err_specs := Specs - ). - -add_unravel_oom_errs(one_or_more(HeadSpec, TailSpecs), !UrInfo) :- - Specs0 = !.UrInfo ^ ui_err_specs, - Specs = [HeadSpec | TailSpecs] ++ Specs0, - !UrInfo ^ ui_err_specs := Specs. - -add_unravel_warn(NewSpec, !UrInfo) :- - Specs0 = !.UrInfo ^ ui_warn_specs, - Specs = [NewSpec | Specs0], - !UrInfo ^ ui_warn_specs := Specs. - -add_unravel_warns(NewSpecs, !UrInfo) :- - Specs0 = !.UrInfo ^ ui_warn_specs, - Specs = NewSpecs ++ Specs0, - !UrInfo ^ ui_warn_specs := Specs. - %---------------------------------------------------------------------------% % % Define the main data structures used by the implementation of state vars. @@ -689,6 +528,31 @@ is_prog_var_for_state_var(VarSet, SVarName, Var) :- % (Initial if _N is zero, and middle otherwise) ). +%---------------------------------------------------------------------------% + +make_svar_magically_known(StateVar, !SVarState, !UrInfo) :- + !.SVarState = 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, !UrInfo), + Status = status_known(Var), + map.set(StateVar, Status, StatusMap0, StatusMap), + !:SVarState = svar_state(StatusMap) + ). + %---------------------------------------------------------------------------% % % Expand !S into !.S, !:S pairs. @@ -1573,14 +1437,16 @@ svar_finish_if_then_else(LocKind, Context, QuantStateVars, ; ThenMissingInits = [_ | _], report_missing_inits_in_ite(Context, ThenMissingInits, - "succeeds", "fails", !UrInfo) + "succeeds", "fails", MissingInThenSpec), + store_missing_init_in_ite_report(MissingInThenSpec, !UrInfo) ), ( ElseMissingInits = [] ; ElseMissingInits = [_ | _], report_missing_inits_in_ite(Context, ElseMissingInits, - "fails", "succeeds", !UrInfo) + "fails", "succeeds", MissingInElseSpec), + store_missing_init_in_ite_report(MissingInElseSpec, !UrInfo) ), svar_goal_to_conj_list(ThenGoal0, ThenGoals0, !UrInfo), @@ -1609,6 +1475,14 @@ svar_finish_if_then_else(LocKind, Context, QuantStateVars, LastIdMap0, SVarSpecs0), !UrInfo ^ ui_state_var_store := SVarStore. +:- pred store_missing_init_in_ite_report(warn_spec::in, + unravel_info::in, unravel_info::out) is det. + +store_missing_init_in_ite_report(Spec, !UrInfo) :- + Specs0 = !.UrInfo ^ ui_state_var_store ^ store_missing_init_specs, + Specs = [Spec | Specs0], + !UrInfo ^ ui_state_var_store ^ store_missing_init_specs := Specs. + :- pred handle_state_vars_in_ite(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, @@ -1998,6 +1872,10 @@ svar_start_outer_atomic_scope(Context, OuterStateVar, OuterDIVar, OuterUOVar, OuterScopeInfo = no_svar_outer_atomic_scope_info ). +:- func ro_construct_name(readonly_context_kind) = string. + +ro_construct_name(roc_lambda) = "lambda expression". + svar_finish_outer_atomic_scope(OuterScopeInfo, !SVarState) :- ( OuterScopeInfo = svar_outer_atomic_scope_info(OuterStateVar, @@ -2269,491 +2147,6 @@ add_conjunct_delayed_renames(DelayedRenamingToAdd, Goal0, Goal, Goal = hlds_goal(GoalExpr, GoalInfo) ). -%---------------------------------------------------------------------------% -% -% A post-pass to delete unneeded copy unifications. Such unifications -% can hide singleton variable problems. -% - - % Suppose we have a goal such as this: - % - % ( if p(..., !.S, !:S) then - % - % else - % - % ) - % - % 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 - % , STATE_VARIABLE_S_7 = STATE_VARIABLE_S_6 - % else - % , 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 HeadUnificationsGoal 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 in our caller will put the unification of the state - % var instances representing !.S and !:S *after* HeadUnificationsGoal. - % 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. - % -:- pred delete_unneeded_copy_goals_in_clause(hlds_goal::in, - hlds_goal::in, hlds_goal::out) is det. - -delete_unneeded_copy_goals_in_clause(HeadUnificationsGoal, Goal0, Goal) :- - vars_in_goal(HeadUnificationsGoal, HeadUnificationsGoalVars), - SeenLater0 = HeadUnificationsGoalVars, - delete_unneeded_copy_goals(Goal0, Goal, SeenLater0, _SeenLater). - -:- 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, _, _, _, _), - vars_in_goal(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(_, _, _, _, _, _, _) - ), - vars_in_goal(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, - StateVar, !UrInfo) :- - VarSet = !.UrInfo ^ ui_varset, - Name = varset.lookup_name(VarSet, StateVar), - Pieces1 = [words("Error: you cannot use")] ++ - color_as_incorrect([quote("!:" ++ Name)]) ++ - [words("here due to the surrounding"), words(RO_Construct), - suffix(";"), - words("you may only refer to")] ++ - color_as_correct([quote("!." ++ Name), suffix(".")]) ++ [nl], - Msg1 = msg(Context, Pieces1), - Pieces2 = [words("Here is the surrounding context that makes"), - words("state variable"), quote(Name), words("readonly."), nl], - Msg2 = msg(RO_Context, Pieces2), - Spec = gen_spec($pred, severity_error, phase_pt2h, [Msg1, Msg2]), - add_unravel_err(Spec, !UrInfo). - -:- func ro_construct_name(readonly_context_kind) = string. - -ro_construct_name(roc_lambda) = "lambda expression". - -%---------------------------------------------------------------------------% - -report_illegal_func_svar_result(Context, StateVar, !UrInfo) :- - VarSet = !.UrInfo ^ ui_varset, - Spec = report_illegal_func_svar_result_raw(Context, VarSet, StateVar), - add_unravel_err(Spec, !UrInfo). - -report_illegal_func_svar_result_raw(Context, VarSet, StateVar) = Spec :- - 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: since it represents two arguments, not one,")] ++ - color_as_incorrect([quote("!" ++ Name)]) ++ - [words("cannot be a function result. You probably meant")] ++ - color_as_correct([fixed("!:" ++ Name), suffix(".")]) ++ [nl], - Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces). - -%---------------------------------------------------------------------------% - -report_illegal_bang_svar_lambda_arg(Context, StateVar, !UrInfo) :- - VarSet = !.UrInfo ^ ui_varset, - Spec = report_illegal_bang_svar_lambda_arg_raw(Context, VarSet, StateVar), - add_unravel_err(Spec, !UrInfo). - -report_illegal_bang_svar_lambda_arg_raw(Context, VarSet, StateVar) = Spec :- - Name = varset.lookup_name(VarSet, StateVar), - Pieces = [words("Error:")] ++ - color_as_incorrect([quote("!" ++ Name)]) ++ - [words("cannot be a lambda argument."), nl, - words("Perhaps you meant")] ++ - color_as_correct([quote("!." ++ Name)]) ++ - [words("or")] ++ - color_as_correct([quote("!:" ++ Name), suffix(".")]) ++ [nl], - Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces). - -%---------------------------------------------------------------------------% - -:- pred report_non_visible_state_var(string::in, prog_context::in, svar::in, - unravel_info::in, unravel_info::out) is det. - -report_non_visible_state_var(DorC, Context, StateVar, !UrInfo) :- - VarSet = !.UrInfo ^ ui_varset, - Name = varset.lookup_name(VarSet, StateVar), - Pieces = [words("Error: state variable")] ++ - color_as_incorrect([quote("!" ++ DorC ++ Name)]) ++ - [words("is not visible in this context."), nl], - Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces), - add_unravel_err(Spec, !UrInfo). - -%---------------------------------------------------------------------------% - -:- pred report_uninitialized_state_var(option::in, prog_context::in, svar::in, - unravel_info::in, unravel_info::out) is det. - -report_uninitialized_state_var(WarnOption, Context, StateVar, !UrInfo) :- - VarSet = !.UrInfo ^ ui_varset, - Name = varset.lookup_name(VarSet, StateVar), - Pieces = [words("Warning: you cannot refer to")] ++ - color_as_subject([quote("!." ++ Name)]) ++ - [words("here, because that state variable has")] ++ - color_as_incorrect([words("not been initialized")]) ++ - [words("yet."), nl], - Spec = spec($pred, severity_warning(WarnOption), phase_pt2h, - Context, Pieces), - add_unravel_warn(Spec, !UrInfo). - -%---------------------------------------------------------------------------% - -:- pred report_repeated_head_state_var(prog_context::in, svar::in, - unravel_info::in, unravel_info::out) is det. - -report_repeated_head_state_var(Context, StateVar, !UrInfo) :- - VarSet = !.UrInfo ^ ui_varset, - Name = varset.lookup_name(VarSet, StateVar), - Pieces = [words("Warning: clause head introduces")] ++ - color_as_incorrect([words("state variable"), quote(Name)]) ++ - [words("more than once."), nl], - Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces), - add_unravel_err(Spec, !UrInfo). - -%---------------------------------------------------------------------------% - -:- pred report_state_var_shadow(prog_context::in, svar::in, - unravel_info::in, unravel_info::out) is det. - -report_state_var_shadow(Context, StateVar, !UrInfo) :- - VarSet = !.UrInfo ^ ui_varset, - Name = varset.lookup_name(VarSet, StateVar), - Pieces = [words("Warning: new state variable")] ++ - color_as_subject([quote(Name)]) ++ - color_as_incorrect([words("shadows old one.")]) ++ [nl], - Spec = spec($pred, severity_warning(warn_state_var_shadowing), phase_pt2h, - Context, Pieces), - add_unravel_warn(Spec, !UrInfo). - -%---------------------------------------------------------------------------% - -:- pred report_missing_inits_in_ite(prog_context::in, list(string)::in, - string::in, string::in, unravel_info::in, unravel_info::out) is det. - -report_missing_inits_in_ite(Context, NextStateVars, - WhenMissing, WhenNotMissing, !UrInfo) :- - NextStateVarsPieces = quote_list_to_color_pieces(color_subject, "and", - [suffix(",")], NextStateVars), - Pieces = [words("When the condition"), words(WhenNotMissing), suffix(","), - words("the if-then-else")] ++ - color_as_inconsistent([words("defines")]) ++ - NextStateVarsPieces ++ - [words("but when the condition"), words(WhenMissing), suffix(",")] ++ - color_as_inconsistent([words("it does not.")]) ++ [nl], - Spec = spec($pred, severity_warning(warn_missing_state_var_init), - phase_pt2h, Context, Pieces), - Specs0 = !.UrInfo ^ ui_state_var_store ^ store_missing_init_specs, - Specs = [Spec | Specs0], - !UrInfo ^ ui_state_var_store ^ store_missing_init_specs := Specs. - -:- pred report_missing_inits_in_disjunct(prog_context::in, list(string)::in, - list(warn_spec)::in, list(warn_spec)::out) is det. - -report_missing_inits_in_disjunct(Context, NextStateVars, !Specs) :- - Pieces = [words("Other disjuncts define")] ++ - quote_list_to_color_pieces(color_subject, "and", [suffix(",")], - NextStateVars) ++ - color_as_incorrect([words("but not this one.")]) ++ [nl], - Spec = spec($pred, severity_warning(warn_missing_state_var_init), - phase_pt2h, Context, Pieces), - % The intention is that our caller got !.Specs from the state var store's - % store_missing_init_specs field, and will put the updated list back there. - !:Specs = [Spec | !.Specs]. - -%---------------------------------------------------------------------------% - -report_svar_unify_error(Context, StateVar, !SVarState, !UrInfo) :- - VarSet = !.UrInfo ^ ui_varset, - Name = varset.lookup_name(VarSet, StateVar), - Pieces = [words("Error:")] ++ - color_as_incorrect([fixed("!" ++ Name)]) ++ - [words("cannot appear as a unification argument."), nl, - words("You probably meant")] ++ - color_as_correct([fixed("!." ++ Name)]) ++ [words("or")] ++ - color_as_correct([fixed("!:" ++ Name), suffix(".")]) ++ [nl], - Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces), - add_unravel_err(Spec, !UrInfo), - !.SVarState = 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, !UrInfo), - Status = status_known(Var), - map.set(StateVar, Status, StatusMap0, StatusMap), - !:SVarState = svar_state(StatusMap) - ). - %---------------------------------------------------------------------------% :- pred find_unused_statevar_args(prog_varset::in, new_statevar_map::in, @@ -2793,113 +2186,6 @@ record_statevar_if_unused(VarSet, LastIdMap, SVar, OoMArgPos, true ). -%---------------------------------------------------------------------------% - -:- pred report_any_unneeded_svars_in_lambda(prog_context::in, - list(mer_mode)::in, goal::in, hlds_goal::in, unused_statevar_arg_map::in, - unravel_info::in, unravel_info::out) is det. - -report_any_unneeded_svars_in_lambda(Context, Modes, ParseTreeGoal, Goal, - UnusedSVarArgMap, !UrInfo) :- - ( if map.is_empty(UnusedSVarArgMap) then - true - else - VarSet = !.UrInfo ^ ui_varset, - non_svar_copy_vars_in_goal(Goal, GoalVarsSet), - set_of_var.to_sorted_list(GoalVarsSet, GoalVars), - list.filter_map(is_prog_var_for_some_state_var(VarSet), - GoalVars, GoalVarSVarNames), - map.foldl( - report_unneeded_svar_in_lambda(Context, Modes, - ParseTreeGoal, GoalVarSVarNames), - UnusedSVarArgMap, !UrInfo) - ). - -:- pred report_unneeded_svar_in_lambda(prog_context::in, list(mer_mode)::in, - goal::in, list(string)::in, uint::in, statevar_arg_desc::in, - unravel_info::in, unravel_info::out) is det. - -report_unneeded_svar_in_lambda(Context, Modes, ParseTreeGoal, GoalVarSVarNames, - ArgNum, SVarArgDesc, !UrInfo) :- - SVarArgDesc = statevar_arg_desc(InitOrFinal, SVarName), - % Please keep the wording of the three warnings generated here - % in sync with the code of the following predicates in pre_typecheck.m: - % - warn_about_any_unneeded_initial_statevars - % - warn_about_unneeded_final_statevar - % - warn_about_unneeded_initial_final_statevar. - ( - ( InitOrFinal = init_arg_only, Prefix = "!." - ; InitOrFinal = final_arg_only, Prefix = "!:" - ), - Pieces = [words("Warning: the state variable")] ++ - color_as_subject([quote(Prefix ++ SVarName)]) ++ [words("is")] ++ - color_as_incorrect([words("never updated")]) ++ - [words("in this lambda expressions, so it should be"), - words("replaced with an ordinary variable."), nl], - Severity = severity_warning(warn_unneeded_initial_statevars_lambda), - Spec = spec($pred, Severity, phase_pt2h, Context, Pieces), - add_unravel_warn(Spec, !UrInfo) - ; - InitOrFinal = init_and_final_arg(_), - % Please keep this wording in sync with the code of the - % warn_about_unneeded_final_statevar predicate in pre_typecheck.m. - InitOrFinal = init_and_final_arg(FinalArgNum), - ( if list.member(SVarName, GoalVarSVarNames) then - % The initial version of SVarName is used by user-written code - % in the lambda goal, so only the final version of SVarName - % is unneeded. - ModuleInfo = !.UrInfo ^ ui_module_info, - FinalArgNumI = uint.cast_to_int(FinalArgNum), - InitArgNumI = uint.cast_to_int(ArgNum), - list.det_index1(Modes, InitArgNumI, InitArgMode), - list.det_index1(Modes, FinalArgNumI, FinalArgMode), - ( if - % See the comments in warn_about_any_unneeded_statevars - % for the reasoning behind this test. - % - % Note that we cannot test the HLDS goal from which our caller - % derived GoalVarSVarNames, because that contains the - % unifications implicitly added by the state variable - % transformation itself. We need the goal from *before* - % that transformation. - not ( ParseTreeGoal = true_expr(_) ), - % See the comments in maybe_warn_about_unneeded_final_statevar - % for the reasoning behind this test. - mode_is_free_of_uniqueness(ModuleInfo, InitArgMode), - mode_is_free_of_uniqueness(ModuleInfo, FinalArgMode) - then - Pieces = [words("Warning: the argument")] ++ - color_as_subject([quote("!:" ++ SVarName)]) ++ - [words("in this lambda expression")] ++ - color_as_incorrect([words("could be deleted,")]) ++ - [words("because its value"), - words("is always the same as its initial value."), nl], - Severity = - severity_warning(warn_unneeded_final_statevars_lambda), - Spec = spec($pred, Severity, phase_pt2h, Context, Pieces), - add_unravel_warn(Spec, !UrInfo) - else - true - ) - else - % The initial version of SVarName is NOT used by user-written code - % in the lambda goal, so both the initial and final versions - % of SVarName are unneeded. - Pieces = [words("Warning: the arguments")] ++ - color_as_subject([quote("!." ++ SVarName)]) ++ - [words("and")] ++ - color_as_subject([quote("!:" ++ SVarName)]) ++ - [words("in this lambda expression")] ++ - color_as_incorrect([words("could be deleted,")]) ++ - [words("because they are not used in the lambda goal,"), - words("and because the final value"), - words("is always the same as the initial value."), nl], - Severity = severity_warning(warn_unneeded_final_statevars_lambda), - Spec = spec($pred, Severity, phase_pt2h, Context, Pieces), - add_unravel_warn(Spec, !UrInfo) - ) - ). - %---------------------------------------------------------------------------% :- end_module hlds.make_hlds.state_var. %---------------------------------------------------------------------------% diff --git a/compiler/superhomogeneous.m b/compiler/superhomogeneous.m index 3d6173dfa..93d0b4fa3 100644 --- a/compiler/superhomogeneous.m +++ b/compiler/superhomogeneous.m @@ -28,6 +28,7 @@ :- import_module hlds.hlds_goal. :- import_module hlds.make_hlds.state_var. :- import_module hlds.make_hlds.superhomogeneous_util. +:- import_module hlds.make_hlds.unravel_info. :- import_module parse_tree. :- import_module parse_tree.prog_data. diff --git a/compiler/superhomogeneous_lambda.m b/compiler/superhomogeneous_lambda.m index bf9ea5ef2..2d059e620 100644 --- a/compiler/superhomogeneous_lambda.m +++ b/compiler/superhomogeneous_lambda.m @@ -22,6 +22,7 @@ :- import_module hlds.hlds_goal. :- import_module hlds.make_hlds.state_var. :- import_module hlds.make_hlds.superhomogeneous_util. +:- import_module hlds.make_hlds.unravel_info. :- import_module parse_tree. :- import_module parse_tree.prog_data. diff --git a/compiler/superhomogeneous_util.m b/compiler/superhomogeneous_util.m index 7b2f6bc88..755c124f9 100644 --- a/compiler/superhomogeneous_util.m +++ b/compiler/superhomogeneous_util.m @@ -19,6 +19,7 @@ :- import_module hlds.hlds_goal. :- import_module hlds.make_hlds.state_var. +:- import_module hlds.make_hlds.unravel_info. :- import_module mdbcomp. :- import_module mdbcomp.prim_data. :- import_module parse_tree. diff --git a/compiler/unravel_info.m b/compiler/unravel_info.m new file mode 100644 index 000000000..f37f212a3 --- /dev/null +++ b/compiler/unravel_info.m @@ -0,0 +1,511 @@ +%---------------------------------------------------------------------------% +% vim: ft=mercury ts=4 sw=4 et +%---------------------------------------------------------------------------% +% Copyright (C) 2005-2011 The University of Melbourne. +% Copyright (C) 2014-2016, 2018-2026 The Mercury team. +% This file may only be copied under the terms of the GNU General +% Public License - see the file COPYING in the Mercury distribution. +%---------------------------------------------------------------------------% +% +% File: state_var.m. +% Main author of original version: rafe. +% Main author of the current version, rewritten in 2011: zs. +% +% This module defines the unravel_info type, which is the representation +% of the state of the transformation to superhomogeneous form, and some of +% the operations on it. These consist mostly of creating new variables, +% generating diagnostics, and recording them in the unravel_info. +% +%---------------------------------------------------------------------------% + +:- module hlds.make_hlds.unravel_info. +:- interface. + +:- import_module hlds.hlds_clauses. +:- import_module hlds.hlds_goal. +:- import_module hlds.hlds_module. +:- import_module hlds.make_hlds.qual_info. +:- import_module hlds.make_hlds.state_var. +:- import_module libs. +:- import_module libs.options. +:- import_module mdbcomp. +:- import_module mdbcomp.prim_data. +:- import_module parse_tree. +:- import_module parse_tree.error_spec. +:- import_module parse_tree.prog_data. +:- import_module parse_tree.prog_item. + +:- import_module list. +:- import_module one_or_more. + +%---------------------------------------------------------------------------% + + % This type describes the state of the code that converts goals + % from their parse tree form to their HLDS form. Almost all the code + % in all the modules of the make_hlds package that handle goals + % pass around values of this type as effectively global state, + % with persistent updates. (The state of the state var transformation + % itself is threaded through that code in a different manner; + % see the definition of the svar_state type below.) + % + % With one exception, all of the fields are writeable. +:- type unravel_info + ---> unravel_info( + % The module_info, which we use for several purposes. + % Most uses are readonly, including getting the globals + % for option lookup, and the module name for creating + % debug output streams. + % + % The only situation in which we update the module_info field + % is when processing disable_warning scopes that disable + % the warning for occurs check violations. In that case, + % we set the option controlling that warning to "no" + % while processing the goal in the scope, and reset it + % afterwards. Such scopes are rare enough that storing the + % value of that option as a separate field in this structure + % would not be worthwhile. + ui_module_info :: module_info, + + % The value of the from_ground_term_threshold option. + % This field duplicates the value stored in the globals + % structure, but it is needed often enough that a separate + % fast-access copy is worthwhile. + % This field is read-only. + ui_fgt_threshold :: int, + + % The store where we record information about what entities + % imported from other modules are used. We use that info + % to generate warnings about unused imports. + ui_qual_info :: qual_info, + + % The varset of the clause whose goal we are converting. + % New instances of state variables are allocated from here. + ui_varset :: prog_varset, + + % The part of the state of the state var transformation + % that is updated persistently (meaning, that once we create + % a new version, we don't go back to look at previous + % versions.) + ui_state_var_store :: svar_store, + + % The errors and warnings that we definitely want to print. + % (The svar_store also contains warn_specs, but we print those + % only as hints *if and when* we later find certain other kinds + % of errors.) + ui_err_specs :: list(err_spec), + ui_warn_specs :: list(warn_spec) + ). + +%---------------------------------------------------------------------------% + +:- pred create_new_unravel_var(prog_var::out, + unravel_info::in, unravel_info::out) is det. + +:- pred create_new_named_unravel_var(string::in, prog_var::out, + unravel_info::in, unravel_info::out) is det. + +:- pred record_unravel_found_syntax_error( + unravel_info::in, unravel_info::out) is det. + +:- pred add_unravel_err(err_spec::in, + unravel_info::in, unravel_info::out) is det. +:- pred add_unravel_errs(list(err_spec)::in, + unravel_info::in, unravel_info::out) is det. +:- pred add_unravel_oom_errs(one_or_more(err_spec)::in, + unravel_info::in, unravel_info::out) is det. + +:- pred add_unravel_warn(warn_spec::in, + unravel_info::in, unravel_info::out) is det. +:- pred add_unravel_warns(list(warn_spec)::in, + unravel_info::in, unravel_info::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, svar::in, + unravel_info::in, unravel_info::out) is det. + +:- pred report_illegal_func_svar_result(prog_context::in, svar::in, + unravel_info::in, unravel_info::out) is det. +:- func report_illegal_func_svar_result_raw(prog_context, + prog_varset, svar) = err_spec. + +:- pred report_illegal_bang_svar_lambda_arg(prog_context::in, svar::in, + unravel_info::in, unravel_info::out) is det. +:- func report_illegal_bang_svar_lambda_arg_raw(prog_context, + prog_varset, svar) = err_spec. + +:- pred report_non_visible_state_var(string::in, prog_context::in, svar::in, + unravel_info::in, unravel_info::out) is det. + +:- pred report_uninitialized_state_var(option::in, prog_context::in, svar::in, + unravel_info::in, unravel_info::out) is det. + +:- pred report_repeated_head_state_var(prog_context::in, svar::in, + unravel_info::in, unravel_info::out) is det. + +:- pred report_state_var_shadow(prog_context::in, svar::in, + unravel_info::in, unravel_info::out) is det. + +:- pred report_missing_inits_in_ite(prog_context::in, list(string)::in, + string::in, string::in, warn_spec::out) is det. + +:- pred report_missing_inits_in_disjunct(prog_context::in, list(string)::in, + list(warn_spec)::in, list(warn_spec)::out) is det. + +:- pred report_svar_unify_error(prog_context::in, svar::in, + unravel_info::in, unravel_info::out) is det. + +:- pred report_any_unneeded_svars_in_lambda(prog_context::in, + list(mer_mode)::in, goal::in, hlds_goal::in, unused_statevar_arg_map::in, + unravel_info::in, unravel_info::out) is det. + +%---------------------------------------------------------------------------% +%---------------------------------------------------------------------------% + +:- implementation. + +:- import_module hlds.goal_vars. +:- import_module hlds.mode_util. +:- import_module parse_tree.set_of_var. + +:- import_module bool. +:- import_module map. +:- import_module string. +:- import_module term. +:- import_module term_context. +:- import_module uint. +:- import_module varset. + +%---------------------------------------------------------------------------% + +create_new_unravel_var(Var, !UrInfo) :- + VarSet0 = !.UrInfo ^ ui_varset, + varset.new_var(Var, VarSet0, VarSet), + !UrInfo ^ ui_varset := VarSet. + +create_new_named_unravel_var(Name, Var, !UrInfo) :- + VarSet0 = !.UrInfo ^ ui_varset, + varset.new_named_var(Name, Var, VarSet0, VarSet), + !UrInfo ^ ui_varset := VarSet. + +record_unravel_found_syntax_error(!UrInfo) :- + QualInfo0 = !.UrInfo ^ ui_qual_info, + qual_info_set_found_syntax_error(yes, QualInfo0, QualInfo), + !UrInfo ^ ui_qual_info := QualInfo. + +add_unravel_err(NewSpec, !UrInfo) :- + Specs0 = !.UrInfo ^ ui_err_specs, + Specs = [NewSpec | Specs0], + !UrInfo ^ ui_err_specs := Specs. + +add_unravel_errs(NewSpecs, !UrInfo) :- + ( + NewSpecs = [] + ; + NewSpecs = [_ | _], + Specs0 = !.UrInfo ^ ui_err_specs, + Specs = NewSpecs ++ Specs0, + !UrInfo ^ ui_err_specs := Specs + ). + +add_unravel_oom_errs(one_or_more(HeadSpec, TailSpecs), !UrInfo) :- + Specs0 = !.UrInfo ^ ui_err_specs, + Specs = [HeadSpec | TailSpecs] ++ Specs0, + !UrInfo ^ ui_err_specs := Specs. + +add_unravel_warn(NewSpec, !UrInfo) :- + Specs0 = !.UrInfo ^ ui_warn_specs, + Specs = [NewSpec | Specs0], + !UrInfo ^ ui_warn_specs := Specs. + +add_unravel_warns(NewSpecs, !UrInfo) :- + Specs0 = !.UrInfo ^ ui_warn_specs, + Specs = NewSpecs ++ Specs0, + !UrInfo ^ ui_warn_specs := Specs. + +%---------------------------------------------------------------------------% +% +% 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, + StateVar, !UrInfo) :- + VarSet = !.UrInfo ^ ui_varset, + Name = varset.lookup_name(VarSet, StateVar), + Pieces1 = [words("Error: you cannot use")] ++ + color_as_incorrect([quote("!:" ++ Name)]) ++ + [words("here due to the surrounding"), words(RO_Construct), + suffix(";"), + words("you may only refer to")] ++ + color_as_correct([quote("!." ++ Name), suffix(".")]) ++ [nl], + Msg1 = msg(Context, Pieces1), + Pieces2 = [words("Here is the surrounding context that makes"), + words("state variable"), quote(Name), words("readonly."), nl], + Msg2 = msg(RO_Context, Pieces2), + Spec = gen_spec($pred, severity_error, phase_pt2h, [Msg1, Msg2]), + add_unravel_err(Spec, !UrInfo). + +%---------------------------------------------------------------------------% + +report_illegal_func_svar_result(Context, StateVar, !UrInfo) :- + VarSet = !.UrInfo ^ ui_varset, + Spec = report_illegal_func_svar_result_raw(Context, VarSet, StateVar), + add_unravel_err(Spec, !UrInfo). + +report_illegal_func_svar_result_raw(Context, VarSet, StateVar) = Spec :- + 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: since it represents two arguments, not one,")] ++ + color_as_incorrect([quote("!" ++ Name)]) ++ + [words("cannot be a function result. You probably meant")] ++ + color_as_correct([fixed("!:" ++ Name), suffix(".")]) ++ [nl], + Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces). + +%---------------------------------------------------------------------------% + +report_illegal_bang_svar_lambda_arg(Context, StateVar, !UrInfo) :- + VarSet = !.UrInfo ^ ui_varset, + Spec = report_illegal_bang_svar_lambda_arg_raw(Context, VarSet, StateVar), + add_unravel_err(Spec, !UrInfo). + +report_illegal_bang_svar_lambda_arg_raw(Context, VarSet, StateVar) = Spec :- + Name = varset.lookup_name(VarSet, StateVar), + Pieces = [words("Error:")] ++ + color_as_incorrect([quote("!" ++ Name)]) ++ + [words("cannot be a lambda argument."), nl, + words("Perhaps you meant")] ++ + color_as_correct([quote("!." ++ Name)]) ++ + [words("or")] ++ + color_as_correct([quote("!:" ++ Name), suffix(".")]) ++ [nl], + Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces). + +%---------------------------------------------------------------------------% + +report_non_visible_state_var(DorC, Context, StateVar, !UrInfo) :- + VarSet = !.UrInfo ^ ui_varset, + Name = varset.lookup_name(VarSet, StateVar), + Pieces = [words("Error: state variable")] ++ + color_as_incorrect([quote("!" ++ DorC ++ Name)]) ++ + [words("is not visible in this context."), nl], + Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces), + add_unravel_err(Spec, !UrInfo). + +%---------------------------------------------------------------------------% + +report_uninitialized_state_var(WarnOption, Context, StateVar, !UrInfo) :- + VarSet = !.UrInfo ^ ui_varset, + Name = varset.lookup_name(VarSet, StateVar), + Pieces = [words("Warning: you cannot refer to")] ++ + color_as_subject([quote("!." ++ Name)]) ++ + [words("here, because that state variable has")] ++ + color_as_incorrect([words("not been initialized")]) ++ + [words("yet."), nl], + Spec = spec($pred, severity_warning(WarnOption), phase_pt2h, + Context, Pieces), + add_unravel_warn(Spec, !UrInfo). + +%---------------------------------------------------------------------------% + +report_repeated_head_state_var(Context, StateVar, !UrInfo) :- + VarSet = !.UrInfo ^ ui_varset, + Name = varset.lookup_name(VarSet, StateVar), + Pieces = [words("Warning: clause head introduces")] ++ + color_as_incorrect([words("state variable"), quote(Name)]) ++ + [words("more than once."), nl], + Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces), + add_unravel_err(Spec, !UrInfo). + +%---------------------------------------------------------------------------% + +report_state_var_shadow(Context, StateVar, !UrInfo) :- + VarSet = !.UrInfo ^ ui_varset, + Name = varset.lookup_name(VarSet, StateVar), + Pieces = [words("Warning: new state variable")] ++ + color_as_subject([quote(Name)]) ++ + color_as_incorrect([words("shadows old one.")]) ++ [nl], + Spec = spec($pred, severity_warning(warn_state_var_shadowing), phase_pt2h, + Context, Pieces), + add_unravel_warn(Spec, !UrInfo). + +%---------------------------------------------------------------------------% + +report_missing_inits_in_ite(Context, NextStateVars, + WhenMissing, WhenNotMissing, Spec) :- + NextStateVarsPieces = quote_list_to_color_pieces(color_subject, "and", + [suffix(",")], NextStateVars), + Pieces = [words("When the condition"), words(WhenNotMissing), suffix(","), + words("the if-then-else")] ++ + color_as_inconsistent([words("defines")]) ++ + NextStateVarsPieces ++ + [words("but when the condition"), words(WhenMissing), suffix(",")] ++ + color_as_inconsistent([words("it does not.")]) ++ [nl], + Spec = spec($pred, severity_warning(warn_missing_state_var_init), + phase_pt2h, Context, Pieces). + +report_missing_inits_in_disjunct(Context, NextStateVars, !Specs) :- + Pieces = [words("Other disjuncts define")] ++ + quote_list_to_color_pieces(color_subject, "and", [suffix(",")], + NextStateVars) ++ + color_as_incorrect([words("but not this one.")]) ++ [nl], + Spec = spec($pred, severity_warning(warn_missing_state_var_init), + phase_pt2h, Context, Pieces), + % The intention is that our caller got !.Specs from the state var store's + % store_missing_init_specs field, and will put the updated list back there. + !:Specs = [Spec | !.Specs]. + +%---------------------------------------------------------------------------% + +report_svar_unify_error(Context, StateVar, !UrInfo) :- + VarSet = !.UrInfo ^ ui_varset, + Name = varset.lookup_name(VarSet, StateVar), + Pieces = [words("Error:")] ++ + color_as_incorrect([fixed("!" ++ Name)]) ++ + [words("cannot appear as a unification argument."), nl, + words("You probably meant")] ++ + color_as_correct([fixed("!." ++ Name)]) ++ [words("or")] ++ + color_as_correct([fixed("!:" ++ Name), suffix(".")]) ++ [nl], + Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces), + add_unravel_err(Spec, !UrInfo). + +%---------------------------------------------------------------------------% + +report_any_unneeded_svars_in_lambda(Context, Modes, ParseTreeGoal, Goal, + UnusedSVarArgMap, !UrInfo) :- + ( if map.is_empty(UnusedSVarArgMap) then + true + else + VarSet = !.UrInfo ^ ui_varset, + non_svar_copy_vars_in_goal(Goal, GoalVarsSet), + set_of_var.to_sorted_list(GoalVarsSet, GoalVars), + list.filter_map(is_prog_var_for_some_state_var(VarSet), + GoalVars, GoalVarSVarNames), + map.foldl( + report_unneeded_svar_in_lambda(Context, Modes, + ParseTreeGoal, GoalVarSVarNames), + UnusedSVarArgMap, !UrInfo) + ). + +:- pred report_unneeded_svar_in_lambda(prog_context::in, list(mer_mode)::in, + goal::in, list(string)::in, uint::in, statevar_arg_desc::in, + unravel_info::in, unravel_info::out) is det. + +report_unneeded_svar_in_lambda(Context, Modes, ParseTreeGoal, GoalVarSVarNames, + ArgNum, SVarArgDesc, !UrInfo) :- + SVarArgDesc = statevar_arg_desc(InitOrFinal, SVarName), + % Please keep the wording of the three warnings generated here + % in sync with the code of the following predicates in pre_typecheck.m: + % - warn_about_any_unneeded_initial_statevars + % - warn_about_unneeded_final_statevar + % - warn_about_unneeded_initial_final_statevar. + ( + ( InitOrFinal = init_arg_only, Prefix = "!." + ; InitOrFinal = final_arg_only, Prefix = "!:" + ), + Pieces = [words("Warning: the state variable")] ++ + color_as_subject([quote(Prefix ++ SVarName)]) ++ [words("is")] ++ + color_as_incorrect([words("never updated")]) ++ + [words("in this lambda expressions, so it should be"), + words("replaced with an ordinary variable."), nl], + Severity = severity_warning(warn_unneeded_initial_statevars_lambda), + Spec = spec($pred, Severity, phase_pt2h, Context, Pieces), + add_unravel_warn(Spec, !UrInfo) + ; + InitOrFinal = init_and_final_arg(_), + % Please keep this wording in sync with the code of the + % warn_about_unneeded_final_statevar predicate in pre_typecheck.m. + InitOrFinal = init_and_final_arg(FinalArgNum), + ( if list.member(SVarName, GoalVarSVarNames) then + % The initial version of SVarName is used by user-written code + % in the lambda goal, so only the final version of SVarName + % is unneeded. + ModuleInfo = !.UrInfo ^ ui_module_info, + FinalArgNumI = uint.cast_to_int(FinalArgNum), + InitArgNumI = uint.cast_to_int(ArgNum), + list.det_index1(Modes, InitArgNumI, InitArgMode), + list.det_index1(Modes, FinalArgNumI, FinalArgMode), + ( if + % See the comments in warn_about_any_unneeded_statevars + % for the reasoning behind this test. + % + % Note that we cannot test the HLDS goal from which our caller + % derived GoalVarSVarNames, because that contains the + % unifications implicitly added by the state variable + % transformation itself. We need the goal from *before* + % that transformation. + not ( ParseTreeGoal = true_expr(_) ), + % See the comments in maybe_warn_about_unneeded_final_statevar + % for the reasoning behind this test. + mode_is_free_of_uniqueness(ModuleInfo, InitArgMode), + mode_is_free_of_uniqueness(ModuleInfo, FinalArgMode) + then + Pieces = [words("Warning: the argument")] ++ + color_as_subject([quote("!:" ++ SVarName)]) ++ + [words("in this lambda expression")] ++ + color_as_incorrect([words("could be deleted,")]) ++ + [words("because its value"), + words("is always the same as its initial value."), nl], + Severity = + severity_warning(warn_unneeded_final_statevars_lambda), + Spec = spec($pred, Severity, phase_pt2h, Context, Pieces), + add_unravel_warn(Spec, !UrInfo) + else + true + ) + else + % The initial version of SVarName is NOT used by user-written code + % in the lambda goal, so both the initial and final versions + % of SVarName are unneeded. + Pieces = [words("Warning: the arguments")] ++ + color_as_subject([quote("!." ++ SVarName)]) ++ + [words("and")] ++ + color_as_subject([quote("!:" ++ SVarName)]) ++ + [words("in this lambda expression")] ++ + color_as_incorrect([words("could be deleted,")]) ++ + [words("because they are not used in the lambda goal,"), + words("and because the final value"), + words("is always the same as the initial value."), nl], + Severity = severity_warning(warn_unneeded_final_statevars_lambda), + Spec = spec($pred, Severity, phase_pt2h, Context, Pieces), + add_unravel_warn(Spec, !UrInfo) + ) + ). + +%---------------------------------------------------------------------------% +:- end_module hlds.make_hlds.unravel_info. +%---------------------------------------------------------------------------%