Files
mercury/compiler/pre_quantification.m
Zoltan Somogyi 783f4b2be4 Fix singleton warnings for code with 'some [...]' goals.
The code in make_hlds_warn.m that is intended to generate singleton warnings
hasn't ever been able to handle code containing 'some [...]' goals properly.
The reason is that

- add_clause.m invokes make_hlds_warn.m only *after* it does quantification
  on the body of the clause being added to the HLDS, but

- quantification has always replaced all lists of quantified variables
  with the empty list.

This meant that

- we never could report code in which the only occurrence of a variable
  was in a list of quantified variables, which is something we *should*
  warn about, and

- we always did generate a singleton warning for code such as
  "some [Val] map.search(Map, Key, Val)", which is something we *should not*
  warn about.

This diff fixes this problem.

The main change is a mechanism that allows us to tell quantification.m
to keep lists of quantified variables intact. However, since the rest
of the compiler does not react well to these lists not being empty,
this diff

- gets make_hlds_warn.m to report whether the clause body goal, in which
  quantification.m was told to preserve any lists of quantified variables,
  *actually contained* any nonempty lists of quantified variables, and

- if it did, then we invoke quantification.m again, this time telling it
  to nuke all lists of quantified variables.

This nuking has to be done relatively rarely, because only a very small
fraction of clauses contain any explicit quantification.

(An alternative design would be for make_hlds_warn.m to always nuke
any nonempty list of quantified variables it traversed. However, this would
require *always* rebuilding the clause body goal, which would probably
be slower on average.)

The above is the main change in this diff. However, the change that is
responsible for the bulk of the diff is the addition of a flag to
exist_quant scopes to specify whether that scope was created by the user
or by the compiler. This is needed because if make_hlds_warn.m sees code
such as "some [Val] map.search(Map, Key, Val)", it definitely *should*
generate a warning about Val being singleton (if it does not occur outside
this code) if the "some [Val]" scope was put there by the compiler.

compiler/make_hlds_warn.m:
    Treat user-generated exist_quant scopes as before (the old code did
    the right thing to generate warnings, it was just given wrong inputs).
    Treat compiler-generated exist_quant scopes as if they weren't there,
    for warning-generating purposes.

    To make this distinction possible, use separate code to handle
    exist_quant and promise_solutions scopes.

    Record whether the goal traversal has seen any nonempty lists of quantified
    variables, and return this info to the caller in add_clause.m.

    Encode the nonempty nature of a list in the argument structure of a
    predicate.

    Update some obsolete terminology in variable and field names.

    Clarify the logic of some code.

compiler/quantification.m:
    Add the keep_quant/do_not_keep_quant switch described above.

    Add some documentation of the predicates to which it is applicable.

    Add free_goal_expr_vars, a version of free_goal_vars that takes
    only a goal expr, without the goal info. At one point, I thought
    this diff needed it. It does not, so the new function is not used,
    but there is also not much point in deleting it, Simplify the code
    of free_goal_vars, deleting one of its callees after inlining it
    at its only call site.

    Replace a appended-to-at-the-front-and-then-reversed list with a cord.

compiler/hlds_goal.m:
    Add the created-by-user-or-compiler flag to exist_quant scopes.

compiler/add_clause.m:
    Move the code that invokes make_hlds_warn.m to warn about singletons
    into the clauses_info_add_clause predicate, whose subcontractor
    add_clause_transform does the initial quantification. The reason
    for this move is that we have never generated singleton variable warnings
    for clauses that were read in from .opt files, or for clauses which are
    known to have syntax errors. With the new setup, if we such clauses,
    clauses_info_add_clause can, and does, tell add_clause_transform
    to tell quantification.m to nuke lists of quantified variable
    right away. It is only for the clauses we *can* warn about
    that clauses_info_add_clause will tell add_clause_transform
    to keep those variables, and will then itself invoke the code
    in make_hlds_warn.m that warns about singletons, followed, if needed,
    by a var-list-nuking reinvocation of quantification.

    This centralization of the code relevant to warning code in
    clauses_info_add_clause also allows the deletion of several of its
    output arguments, since its two callers used those arguments
    only to invoke the warning-generation code. It also eliminates
    the duplication of code in those two callers.

compiler/instance_method_clauses.m:
    Conform to the change in add_clause.m.

compiler/add_foreign_proc.m:
compiler/assertion.m:
compiler/constraint.m:
compiler/cse_detection.m:
compiler/det_analysis.m:
compiler/det_report.m:
compiler/format_call.m:
compiler/goal_expr_to_goal.m:
compiler/goal_util.m:
compiler/hlds_desc.m:
compiler/hlds_out_goal.m:
compiler/interval.m:
compiler/lambda.m:
compiler/mark_tail_calls.m:
compiler/ml_code_gen.m:
compiler/mode_constraints.m:
compiler/modecheck_goal.m:
compiler/polymorphism_goal.m:
compiler/pre_quantification.m:
compiler/purity.m:
compiler/saved_vars.m:
compiler/simplify_goal_scope.m:
compiler/simplify_proc.m:
compiler/state_var.m:
compiler/stm_expand.m:
compiler/superhomogeneous.m:
compiler/switch_detection.m:
compiler/try_expand.m:
compiler/typecheck.m:
compiler/unique_modes.m:
    Conform to the change in hlds_goal.m and/or quantification.m.

compiler/options.m:
    Add a way to detect the presence of this fix in the installed compiler.

tests/valid/Mmakefile:
    Enable the old test case for this problem, some_singleton,
    which we haven't passed until now.

tests/warnings/Mmakefile:
    Enable the missing_singleton_warning test case, which we haven't passed
    until now.
2023-06-10 09:59:00 +02:00

503 lines
21 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 2017 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: pre_quantification.m.
% Main author: zs.
%
% This module adds implicit quantifications of some variables just inside
% trace goal scopes in order to avoid a minor but annoying problem, which can
% also be confusing for people who come across it the first time.
%
% Consider the following code:
%
% :- pred p(int::in, int::in, int::out) is det.
%
% p(A, B, X) :-
% trace [io(!IO)] (
% ToPrint = A,
% io.format("A = %d\n", [i(ToPrint)], !IO)
% ),
% trace [io(!IO)] (
% ToPrint = B,
% io.format("B = %d\n", [i(ToPrint)], !IO)
% ),
% X = A + B,
% trace [io(!IO)] (
% ToPrint = X,
% io.format("X = %d\n", [i(ToPrint)], !IO)
% ).
%
% Since the variable ToPrint is used inside more than one trace goal,
% the compiler's usual scope rules consider it to be a nonlocal variable
% in each trace goal. This is a problem, because the compiler does not allow
% trace goals to bind variables that are not local to the trace goal.
% It does this to preserve the ability of the program to work whether or not
% the trace goal is there, since it can be deleted if either its compile_time
% or run_time condition turns out to be false.
%
% However, in cases like this, where the nonlocal variable occurs *only*
% inside trace goals, leaving ToPrint unbound if a trace goal is deleted
% will not affect the execution of the program. Having it bound more than once
% if the trace goals *aren't* deleted would be a problem, but this is easily
% fixed by making ToPrint existentially quantified inside each trace goal;
% that way, each trace goal will bind and use its own copy of ToPrint.
%
% The job of this module is to find out which variables in a clause body
% are used only inside trace goals, and to insert the quantifications
% into trace goals as necessary to ensure that all such variables
% will end up being local to each trace goal scope. The part of the compiler
% that ensures this is the quantification pass, which is invoked on the
% goal we generate pretty much immediately after we return it.
%
% The overall effect of putting this pass before the quantification pass
% is a minor tweak of the usual rules of quantification, a tweak that
% makes the rules enforced by the compiler resemble the rules expected
% by programmers more closely, helping the compiler approach closer to the
% principle of least astonishment.
%
%-----------------------------------------------------------------------------%
:- module hlds.pre_quantification.
:- interface.
:- import_module hlds.hlds_goal.
%-----------------------------------------------------------------------------%
:- pred separate_trace_goal_only_locals(hlds_goal::in, hlds_goal::out) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module parse_tree.
:- import_module parse_tree.prog_data.
:- import_module assoc_list.
:- import_module counter.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module sparse_bitset.
:- import_module uint.
%-----------------------------------------------------------------------------%
%
% The pre-quantification algorithm is based on dividing the clause body
% into one or more zones, each identified by a non-negative integer.
% The zones are based on viewing the clause body as a tree:
%
% - with each goal being a node,
% - atomic goals being leaf nodes, and
% - each compound goal having a subtree for each subgoal.
%
% If lambda goals didn't exist, each zone would be identified by its top node.
%
% The top node of zone 0 is the root of the tree, while the top node of
% every other zone is a trace goal, i.e. a scope goal whose scope_reason
% is trace_goal. Every trace goal is the top node of a zone, with the
% exception of trace goals that are contained inside other trace goals.
% (We have no rule against nesting trace goals, though they don't make
% much sense, and I (zs) have never seen such nesting.)
%
% The zone that each node belongs to is the zone whose top node you first find
% when you traverse the tree upwards from the node. In other words, each zone
% claims the nodes beneath it, except the ones that are claimed by another
% zone whose top node is lower down.
%
% Since lambda goals do exist, we have to handle them. We do so by considering
% the lambda goal to be effectively a separate predicate body inside this
% clause (since the compiler will eventually turn it into that). So the
% top level of a rhs_lambda_goal is also considered to be in zone 0.
:- type zone == uint.
:- func top_zone = zone.
:- pragma inline(func(top_zone/0)).
top_zone = 0u.
%-----------------------------------------------------------------------------%
%
% The pre-quantification algorithm has three main steps.
%
% - The first step is to find out which variables are used in which zones.
% This is done by build_vars_to_zones_in_*, which build the VarsToZones map.
%
% - The second step is finding out which variables occur only inside
% trace goals, not outside them. These variables should be local to the
% trace goals they appear in. If they occur in only one trace goal,
% ordinary quantification will ensure this. We need to add explicit
% existential quantifications for them only if they occur inside
% more than one trace goal. In terms of zones, this means variables
% that don't occur in zone 0 but do appear in more than one other zone.
% (We call these variables the "duplicated" variables.) The job of
% build_zones_to_dup_vars is to map each trace goal zone to the list
% of variables that needs to have an existential quantifier added for it
% in the scope of that trace goal, and to return that as ZonesToDupVars.
%
% - The third step is to go through the clause body and insert those
% existential quantification scopes inside every trace goal scope
% that contains one or more of these "duplicated" variables.
%
% Our parent ensures that we don't get called on clauses that
% don't contain any trace goals. Most clauses that have trace goals
% will have only a few of them, so the sparse_bitset(zone) will
% typically contain only a single node.
%
:- type vars_to_zones == map(prog_var, sparse_bitset(zone)).
:- type zones_to_dup_vars == map(zone, list(prog_var)).
separate_trace_goal_only_locals(Goal0, Goal) :-
map.init(VarsToZones0),
counter.uinit(1u, TraceCounter0),
build_vars_to_zones_in_goal(top_zone, Goal0, TraceCounter0, _,
VarsToZones0, VarsToZones),
map.init(ZonesToDupVars0),
map.foldl(build_zones_to_dup_vars, VarsToZones,
ZonesToDupVars0, ZonesToDupVars),
( if map.is_empty(ZonesToDupVars) then
Goal = Goal0
else
add_exist_scopes_for_dup_vars_in_goal(top_zone, ZonesToDupVars,
Goal0, Goal, TraceCounter0, _)
).
%-----------------------------------------------------------------------------%
% The first step described in the comment above
% separate_trace_goal_only_locals.
%
:- pred build_vars_to_zones_in_goal(zone::in, hlds_goal::in,
ucounter::in, ucounter::out, vars_to_zones::in, vars_to_zones::out) is det.
build_vars_to_zones_in_goal(CurZone, Goal, !TraceCounter, !VarsToZones) :-
Goal = hlds_goal(GoalExpr, _GoalInfo),
(
GoalExpr = plain_call(_PredId, _ProcId, ArgVars, _Builtin,
_MaybeUnifyContext, _SymName),
record_vars_in_zone(CurZone, ArgVars, !VarsToZones)
;
GoalExpr = generic_call(GenericCall, ArgVars, _Modes,
_RegTypes, _Detism),
record_vars_in_zone(CurZone, ArgVars, !VarsToZones),
(
GenericCall = higher_order(ClosureVar, _Purity, _CallKind, _Arity),
record_var_in_zone(CurZone, ClosureVar, !VarsToZones)
;
GenericCall = class_method(TypeClassInfoVar, _MethodNum,
_ClassId, _PFSymNameArity),
record_var_in_zone(CurZone, TypeClassInfoVar, !VarsToZones)
;
GenericCall = event_call(_)
;
GenericCall = cast(_)
)
;
GoalExpr = call_foreign_proc(_Attrs, _PredId, _ProcId,
ForeignArgs, ExtraArgs, _TraceCond, _Impl),
record_vars_in_zone(CurZone, list.map(foreign_arg_var, ForeignArgs),
!VarsToZones),
record_vars_in_zone(CurZone, list.map(foreign_arg_var, ExtraArgs),
!VarsToZones)
;
GoalExpr = unify(LHSVar, RHS, _Mode, _Kind, _Context),
record_var_in_zone(CurZone, LHSVar, !VarsToZones),
(
RHS = rhs_var(RHSVar),
record_var_in_zone(CurZone, RHSVar, !VarsToZones)
;
RHS = rhs_functor(_ConsId, _IsExistConstr, RHSArgVars),
record_vars_in_zone(CurZone, RHSArgVars, !VarsToZones)
;
RHS = rhs_lambda_goal(_Purity, _Groundness, _PredOrFunc,
_EvalMethod, _NonLocals, LambdaVarsModes, _Detism,
LambdaGoal),
% The lambda goal is the one clause of the procedure that
% we will construct from LambdaGoal. We therefore treat it
% as we treat the top level goal.
assoc_list.keys(LambdaVarsModes, LambdaVars),
LambdaZone = top_zone,
record_vars_in_zone(LambdaZone, LambdaVars, !VarsToZones),
build_vars_to_zones_in_goal(LambdaZone, LambdaGoal,
!TraceCounter, !VarsToZones)
)
;
( GoalExpr = conj(_ConjType, SubGoals)
; GoalExpr = disj(SubGoals)
),
list.foldl2(build_vars_to_zones_in_goal(CurZone), SubGoals,
!TraceCounter, !VarsToZones)
;
GoalExpr = switch(Var, _CanFail, Cases),
record_var_in_zone(CurZone, Var, !VarsToZones),
list.foldl2(build_vars_to_zones_in_case(CurZone), Cases,
!TraceCounter, !VarsToZones)
;
GoalExpr = negation(SubGoal),
build_vars_to_zones_in_goal(CurZone, SubGoal,
!TraceCounter, !VarsToZones)
;
GoalExpr = scope(Reason, SubGoal),
( if
Reason = trace_goal(_, _, _, _, _),
CurZone = top_zone
then
counter.uallocate(NewZone, !TraceCounter),
build_vars_to_zones_in_goal(NewZone, SubGoal,
!TraceCounter, !VarsToZones)
else
build_vars_to_zones_in_goal(CurZone, SubGoal,
!TraceCounter, !VarsToZones)
)
;
GoalExpr = if_then_else(Vars, Cond, Then, Else),
record_vars_in_zone(CurZone, Vars, !VarsToZones),
build_vars_to_zones_in_goal(CurZone, Cond,
!TraceCounter, !VarsToZones),
build_vars_to_zones_in_goal(CurZone, Then,
!TraceCounter, !VarsToZones),
build_vars_to_zones_in_goal(CurZone, Else,
!TraceCounter, !VarsToZones)
;
GoalExpr = shorthand(ShortHand),
(
ShortHand = bi_implication(SubGoalA, SubGoalB),
build_vars_to_zones_in_goal(CurZone, SubGoalA,
!TraceCounter, !VarsToZones),
build_vars_to_zones_in_goal(CurZone, SubGoalB,
!TraceCounter, !VarsToZones)
;
ShortHand = atomic_goal(_GoalType, OuterVars, InnerVars,
MaybeOutputVars, MainGoal, OrElseGoals, _OrElseInners),
OuterVars = atomic_interface_vars(OVA, OVB),
InnerVars = atomic_interface_vars(IVA, IVB),
record_vars_in_zone(CurZone, [OVA, OVB, IVA, IVB], !VarsToZones),
(
MaybeOutputVars = no
;
MaybeOutputVars = yes(OutputVars),
record_vars_in_zone(CurZone, OutputVars, !VarsToZones)
),
build_vars_to_zones_in_goal(CurZone, MainGoal,
!TraceCounter, !VarsToZones),
list.foldl2(build_vars_to_zones_in_goal(CurZone), OrElseGoals,
!TraceCounter, !VarsToZones)
;
ShortHand = try_goal(MaybeIOStateVars, ResultVar, SubGoal),
(
MaybeIOStateVars = no
;
MaybeIOStateVars = yes(IOStateVars),
IOStateVars = try_io_state_vars(ISVA, ISVB),
record_vars_in_zone(CurZone, [ISVA, ISVB], !VarsToZones)
),
record_var_in_zone(CurZone, ResultVar, !VarsToZones),
build_vars_to_zones_in_goal(CurZone, SubGoal,
!TraceCounter, !VarsToZones)
)
).
:- pred build_vars_to_zones_in_case(zone::in, case::in,
ucounter::in, ucounter::out, vars_to_zones::in, vars_to_zones::out) is det.
build_vars_to_zones_in_case(CurZone, Case, !TraceCounter, !VarsToZones) :-
Case = case(_MainConsId, _OtherConsIds, Goal),
build_vars_to_zones_in_goal(CurZone, Goal, !TraceCounter, !VarsToZones).
:- pred record_vars_in_zone(zone::in, list(prog_var)::in,
vars_to_zones::in, vars_to_zones::out) is det.
record_vars_in_zone(_Zone, [], !VarsToZones).
record_vars_in_zone(Zone, [Var | Vars], !VarsToZones) :-
record_var_in_zone(Zone, Var, !VarsToZones),
record_vars_in_zone(Zone, Vars, !VarsToZones).
:- pred record_var_in_zone(zone::in, prog_var::in,
vars_to_zones::in, vars_to_zones::out) is det.
record_var_in_zone(Zone, Var, !VarsToZones) :-
( if map.search(!.VarsToZones, Var, Zones0) then
% Most variables will occur only in one zone, typically zone 0.
% However, they will typically occur there more than once.
% We don't want to set the bit of the zone more than once.
( if sparse_bitset.insert_new(Zone, Zones0, Zones1) then
map.det_update(Var, Zones1, !VarsToZones)
else
% Zone is already in Zones0.
true
)
else
Zones = sparse_bitset.make_singleton_set(Zone),
map.det_insert(Var, Zones, !VarsToZones)
).
%-----------------------------------------------------------------------------%
% The second step described in the comment above
% separate_trace_goal_only_locals.
%
:- pred build_zones_to_dup_vars(prog_var::in, sparse_bitset(zone)::in,
zones_to_dup_vars::in, zones_to_dup_vars::out) is det.
build_zones_to_dup_vars(Var, Zones, !ZonesToDupVars) :-
ZoneList = sparse_bitset.to_sorted_list(Zones),
( if
ZoneList = [FirstZone, _SecondZone | _],
FirstZone \= top_zone
then
list.foldl(add_var_to_zone(Var), ZoneList, !ZonesToDupVars)
else
true
).
:- pred add_var_to_zone(prog_var::in, zone::in,
zones_to_dup_vars::in, zones_to_dup_vars::out) is det.
add_var_to_zone(Var, Zone, !ZonesToDupVars) :-
( if map.search(!.ZonesToDupVars, Zone, DupVars0) then
DupVars = [Var | DupVars0],
map.det_update(Zone, DupVars, !ZonesToDupVars)
else
map.det_insert(Zone, [Var], !ZonesToDupVars)
).
%-----------------------------------------------------------------------------%
% The third step described in the comment above
% separate_trace_goal_only_locals.
%
:- pred add_exist_scopes_for_dup_vars_in_goal(zone::in, zones_to_dup_vars::in,
hlds_goal::in, hlds_goal::out, ucounter::in, ucounter::out) is det.
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
!Goal, !TraceCounter) :-
!.Goal = hlds_goal(GoalExpr0, GoalInfo),
(
( GoalExpr0 = plain_call(_, _, _, _, _, _)
; GoalExpr0 = generic_call(_, _, _, _, _)
; GoalExpr0 = call_foreign_proc(_, _, _, _, _, _, _)
),
GoalExpr = GoalExpr0
;
GoalExpr0 = unify(LHSVar, RHS0, Mode, Kind, Context),
(
( RHS0 = rhs_var(_)
; RHS0 = rhs_functor(_, _, _)
),
GoalExpr = GoalExpr0
;
RHS0 = rhs_lambda_goal(Purity, Groundness, PredOrFunc,
EvalMethod, NonLocals, LambdaVarsModes, Detism, LambdaGoal0),
LambdaZone = top_zone,
add_exist_scopes_for_dup_vars_in_goal(LambdaZone, ZonesToDupVars,
LambdaGoal0, LambdaGoal, !TraceCounter),
RHS = rhs_lambda_goal(Purity, Groundness, PredOrFunc,
EvalMethod, NonLocals, LambdaVarsModes, Detism, LambdaGoal),
GoalExpr = unify(LHSVar, RHS, Mode, Kind, Context)
)
;
GoalExpr0 = conj(ConjType, SubGoals0),
list.map_foldl(
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars),
SubGoals0, SubGoals, !TraceCounter),
GoalExpr = conj(ConjType, SubGoals)
;
GoalExpr0 = disj(SubGoals0),
list.map_foldl(
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars),
SubGoals0, SubGoals, !TraceCounter),
GoalExpr = disj(SubGoals)
;
GoalExpr0 = switch(Var, CanFail, Cases0),
list.map_foldl(
add_exist_scopes_for_dup_vars_in_case(CurZone, ZonesToDupVars),
Cases0, Cases, !TraceCounter),
GoalExpr = switch(Var, CanFail, Cases)
;
GoalExpr0 = negation(SubGoal0),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
SubGoal0, SubGoal, !TraceCounter),
GoalExpr = negation(SubGoal)
;
GoalExpr0 = scope(Reason, SubGoal0),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
SubGoal0, SubGoal1, !TraceCounter),
( if
Reason = trace_goal(_, _, _, _, _),
CurZone = top_zone
then
counter.uallocate(NewZone, !TraceCounter),
( if map.search(ZonesToDupVars, NewZone, DupVars) then
list.sort(DupVars, SortedDupVars),
QuantReason = exist_quant(SortedDupVars, compiler_quant),
QuantExpr = scope(QuantReason, SubGoal1),
SubGoal1 = hlds_goal(_, SubGoalInfo1),
SubGoal = hlds_goal(QuantExpr, SubGoalInfo1)
else
SubGoal = SubGoal1
)
else
SubGoal = SubGoal1
),
GoalExpr = scope(Reason, SubGoal)
;
GoalExpr0 = if_then_else(Vars, Cond0, Then0, Else0),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
Cond0, Cond, !TraceCounter),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
Then0, Then, !TraceCounter),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
Else0, Else, !TraceCounter),
GoalExpr = if_then_else(Vars, Cond, Then, Else)
;
GoalExpr0 = shorthand(ShortHand0),
(
ShortHand0 = bi_implication(SubGoalA0, SubGoalB0),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
SubGoalA0, SubGoalA, !TraceCounter),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
SubGoalB0, SubGoalB, !TraceCounter),
ShortHand = bi_implication(SubGoalA, SubGoalB)
;
ShortHand0 = atomic_goal(GoalType, OuterVars, InnerVars,
MaybeOutputVars, MainGoal0, OrElseGoals0, OrElseInners),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
MainGoal0, MainGoal, !TraceCounter),
list.map_foldl(
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars),
OrElseGoals0, OrElseGoals, !TraceCounter),
ShortHand = atomic_goal(GoalType, OuterVars, InnerVars,
MaybeOutputVars, MainGoal, OrElseGoals, OrElseInners)
;
ShortHand0 = try_goal(MaybeIOStateVars, ResultVar, SubGoal0),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
SubGoal0, SubGoal, !TraceCounter),
ShortHand = try_goal(MaybeIOStateVars, ResultVar, SubGoal)
),
GoalExpr = shorthand(ShortHand)
),
!:Goal = hlds_goal(GoalExpr, GoalInfo).
:- pred add_exist_scopes_for_dup_vars_in_case(zone::in, zones_to_dup_vars::in,
case::in, case::out, ucounter::in, ucounter::out) is det.
add_exist_scopes_for_dup_vars_in_case(CurZone, ZonesToDupVars,
!Case, !TraceCounter) :-
!.Case = case(MainConsId, OtherConsIds, Goal0),
add_exist_scopes_for_dup_vars_in_goal(CurZone, ZonesToDupVars,
Goal0, Goal, !TraceCounter),
!:Case = case(MainConsId, OtherConsIds, Goal).
%-----------------------------------------------------------------------------%
:- end_module hlds.pre_quantification.
%-----------------------------------------------------------------------------%