Files
mercury/compiler/add_mutable_aux_preds.m
Zoltan Somogyi c643291d1f Add cons_list and snoc_list to cord.m.
library/cord.m:
    Add those two new predicates.

NEWS.md:
    Announce the additions.

compiler/*.m:
    Use snoc_list in many places.

tests/hard_coded/test_cord_2.{m,exp}:
    Extend this test case to test cons_list.
2025-10-10 18:48:26 +11:00

506 lines
22 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2015-2025 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.
%---------------------------------------------------------------------------%
%
% This module creates both the public predicates users use to access mutables,
% and the private auxiliary predicates needed to make that possible.
%
%-----------------------------------------------------------------------------%
%
% Mutables are implemented as a source-to-source transformation on the
% parse tree. The transformation, whose details depend on the compilation
% target, is documented in notes/mutables.
%
%-----------------------------------------------------------------------------%
:- module hlds.make_hlds.add_mutable_aux_preds.
:- interface.
:- import_module hlds.hlds_module.
:- import_module hlds.make_hlds.make_hlds_types.
:- import_module parse_tree.
:- import_module parse_tree.error_spec.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_foreign.
:- import_module parse_tree.prog_item.
:- import_module cord.
:- import_module list.
:- import_module set_tree234.
%---------------------------------------------------------------------------%
% Check the mutables that are local to this module for errors,
% and implement them by transforming them into other kinds of items
% and pred_target_names to be added to the HLDS by our caller.
%
:- pred implement_mutables_if_local(module_info::in,
set_tree234(inst_ctor)::in, sec_list(item_mutable_info)::in,
sec_list(item_pred_decl_info)::out,
ims_list(item_clause_info)::out, ims_list(item_foreign_proc_info)::out,
list(foreign_decl_code)::out, list(foreign_body_code)::out,
cord(impl_pragma_fproc_export_info)::out,
pred_target_names::in, pred_target_names::out,
list(error_spec)::in, list(error_spec)::out) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module backend_libs.
:- import_module backend_libs.foreign.
:- import_module hlds.error_msg_inst.
:- import_module hlds.hlds_inst_mode.
:- import_module hlds.status.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.options.
:- import_module mdbcomp.
:- import_module mdbcomp.builtin_modules.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.prog_mutable.
:- import_module bool.
:- import_module map.
:- import_module require.
:- import_module varset.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
implement_mutables_if_local(ModuleInfo, UndefInstCtors, SecList, PredDecls,
ClauseInfos, ForeignProcs, ForeignDeclCodes, ForeignBodyCodes,
FPEInfoCord, !PredTargetNames, !Specs) :-
implement_mutables_sec_loop(ModuleInfo, UndefInstCtors,
SecList, map.init,
cord.init, PredDeclCord,
cord.init, ClauseInfoCord, cord.init, ForeignProcCord,
cord.init, ForeignDeclCodeCord, cord.init, ForeignBodyCodeCord,
cord.init, FPEInfoCord, !PredTargetNames, !Specs),
PredDecls = cord.list(PredDeclCord),
ClauseInfos = cord.list(ClauseInfoCord),
ForeignProcs = cord.list(ForeignProcCord),
ForeignDeclCodes = cord.list(ForeignDeclCodeCord),
ForeignBodyCodes = cord.list(ForeignBodyCodeCord).
:- pred implement_mutables_sec_loop(module_info::in,
set_tree234(inst_ctor)::in, sec_list(item_mutable_info)::in,
map(string, prog_context)::in,
sec_cord(item_pred_decl_info)::in, sec_cord(item_pred_decl_info)::out,
ims_cord(item_clause_info)::in, ims_cord(item_clause_info)::out,
ims_cord(item_foreign_proc_info)::in,
ims_cord(item_foreign_proc_info)::out,
cord(foreign_decl_code)::in, cord(foreign_decl_code)::out,
cord(foreign_body_code)::in, cord(foreign_body_code)::out,
cord(impl_pragma_fproc_export_info)::in,
cord(impl_pragma_fproc_export_info)::out,
pred_target_names::in, pred_target_names::out,
list(error_spec)::in, list(error_spec)::out) is det.
implement_mutables_sec_loop(_ModuleInfo, _UndefInstCtors, [], _DefinedMutables,
!PredDeclCord, !ClauseInfoCord, !ForeignProcCord,
!ForeignDeclCodeCord, !ForeignBodyCodeCord,
!FPEInfoCord, !PredTargetNames, !Specs).
implement_mutables_sec_loop(ModuleInfo, UndefInstCtors,
[SecSubList | SecSubLists], !.DefinedMutables,
!PredDeclCord, !ClauseInfoCord, !ForeignProcCord,
!ForeignDeclCodeCord, !ForeignBodyCodeCord,
!FPEInfoCord, !PredTargetNames, !Specs) :-
SecSubList = sec_sub_list(SectionInfo, ItemMutables),
SectionInfo = sec_info(ItemMercuryStatus, _NeedQual),
(
ItemMercuryStatus = item_defined_in_this_module(_),
module_info_get_globals(ModuleInfo, Globals),
module_info_get_name(ModuleInfo, ModuleName),
TypeNameFunc = exported_type_to_string(ModuleInfo),
ModuleParams = module_params(Globals, ModuleName, UndefInstCtors,
TypeNameFunc),
implement_mutables_loop(ModuleInfo, SectionInfo, ModuleParams,
ItemMutables, !DefinedMutables, cord.init, PredDeclSubCord,
cord.init, ClauseInfoSubCord, cord.init, ForeignProcSubCord,
!ForeignDeclCodeCord, !ForeignBodyCodeCord,
!FPEInfoCord, !PredTargetNames, !Specs),
PredDeclSubList =
sec_sub_list(SectionInfo, cord.list(PredDeclSubCord)),
cord.snoc(PredDeclSubList, !PredDeclCord),
( if cord.is_empty(ClauseInfoSubCord) then
true
else
ClauseInfoSubList =
ims_sub_list(ItemMercuryStatus, cord.list(ClauseInfoSubCord)),
cord.snoc(ClauseInfoSubList, !ClauseInfoCord)
),
( if cord.is_empty(ForeignProcSubCord) then
true
else
ForeignProcSubList =
ims_sub_list(ItemMercuryStatus, cord.list(ForeignProcSubCord)),
cord.snoc(ForeignProcSubList, !ForeignProcCord)
)
;
ItemMercuryStatus = item_defined_in_other_module(_)
% We don't implement the `mutable' declaration unless it is defined
% in this module. If we did not have this check, we would duplicate
% the definition of the global variable storing the mutable
% in any submodules of the module that actually defined the mutable.
),
implement_mutables_sec_loop(ModuleInfo, UndefInstCtors,
SecSubLists, !.DefinedMutables,
!PredDeclCord, !ClauseInfoCord, !ForeignProcCord,
!ForeignDeclCodeCord, !ForeignBodyCodeCord,
!FPEInfoCord, !PredTargetNames, !Specs).
:- pred implement_mutables_loop(module_info::in, sec_info::in,
module_params::in, list(item_mutable_info)::in,
map(string, prog_context)::in, map(string, prog_context)::out,
cord(item_pred_decl_info)::in, cord(item_pred_decl_info)::out,
cord(item_clause_info)::in, cord(item_clause_info)::out,
cord(item_foreign_proc_info)::in, cord(item_foreign_proc_info)::out,
cord(foreign_decl_code)::in, cord(foreign_decl_code)::out,
cord(foreign_body_code)::in, cord(foreign_body_code)::out,
cord(impl_pragma_fproc_export_info)::in,
cord(impl_pragma_fproc_export_info)::out,
pred_target_names::in, pred_target_names::out,
list(error_spec)::in, list(error_spec)::out) is det.
implement_mutables_loop(_ModuleInfo, _SectionInfo, _ModuleParams,
[], !DefinedMutables, !PredDeclCord, !ClauseInfoCord, !ForeignProcCord,
!ForeignDeclCodeCord, !ForeignBodyCodeCord,
!FPEInfoCord, !PredTargetNames, !Specs).
implement_mutables_loop(ModuleInfo, SectionInfo, ModuleParams,
[ItemMutable | ItemMutables], !DefinedMutables, !PredDeclCord,
!ClauseInfoCord, !ForeignProcCord,
!ForeignDeclCodeCord, !ForeignBodyCodeCord,
!FPEInfoCord, !PredTargetNames, !Specs) :-
MutableName = ItemMutable ^ mut_name,
Context = ItemMutable ^ mut_context,
( if map.search(!.DefinedMutables, MutableName, OldContext) then
Pieces = [words("Error: this definition of the mutable")] ++
color_as_subject([fixed(MutableName)]) ++
[words("is a")] ++
color_as_incorrect([words("duplicate.")]) ++
[nl],
OldPieces = [words("The first definition is here."), nl],
Spec = error_spec($pred, severity_error, phase_pt2h,
[msg(Context, Pieces),
msg(OldContext, OldPieces)]),
!:Specs = [Spec | !.Specs]
else
map.det_insert(MutableName, Context, !DefinedMutables),
check_mutable(ModuleInfo, ModuleParams, ItemMutable, !Specs),
implement_mutable(ModuleParams, ItemMutable,
PredDecls, ClauseInfos, ForeignProcs, FPEInfo,
!ForeignDeclCodeCord, !ForeignBodyCodeCord, !PredTargetNames),
cord.snoc_list(PredDecls, !PredDeclCord),
cord.snoc_list(ClauseInfos, !ClauseInfoCord),
cord.snoc_list(ForeignProcs, !ForeignProcCord),
cord.snoc(FPEInfo, !FPEInfoCord)
),
implement_mutables_loop(ModuleInfo, SectionInfo, ModuleParams,
ItemMutables, !DefinedMutables, !PredDeclCord,
!ClauseInfoCord, !ForeignProcCord,
!ForeignDeclCodeCord, !ForeignBodyCodeCord,
!FPEInfoCord, !PredTargetNames, !Specs).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- pred check_mutable(module_info::in, module_params::in,
item_mutable_info::in, list(error_spec)::in, list(error_spec)::out) is det.
check_mutable(ModuleInfo, ModuleParams, ItemMutable, !Specs) :-
ItemMutable = item_mutable_info(_MutableName,
_OrigType, _Type, OrigInst, Inst,
_InitTerm, _VarSetMutable, MutAttrs, Context, _SeqNum),
module_info_get_globals(ModuleInfo, Globals),
% If the mutable is to be trailed, then we need to be in a trailing grade.
MutAttrs = mutable_var_attributes(_LangMap, Const),
globals.lookup_bool_option(Globals, use_trail, UseTrail),
( if
Const = mutable_is_not_constant(_, Local),
mutable_thread_local_trailed(Local) = mutable_trailed,
UseTrail = no
then
TrailPieces = [words("Error:")] ++
color_as_subject([words("trailed"), decl("mutable"),
words("declaration")]) ++
color_as_incorrect([words("in non-trailing grade.")]) ++ [nl],
TrailSpec = spec($pred, severity_error, phase_pt2h,
Context, TrailPieces),
!:Specs = [TrailSpec | !.Specs]
else
true
),
% Check that the inst in the mutable declaration is a valid inst
% for a mutable declaration.
% It is okay to pass a dummy varset in here since any attempt
% to use inst variables in a mutable declaration should already
% been dealt with when the mutable declaration was parsed.
DummyInstVarSet = varset.init,
check_mutable_inst(ModuleInfo, ModuleParams, Context, DummyInstVarSet,
[], Inst, [], ExpandedInstSpecs),
(
ExpandedInstSpecs = []
;
ExpandedInstSpecs = [_ | _],
% We found some insts in Inst that are not allowed in mutables.
%
% Inst has been processed by equiv_type.m, which replaces named insts
% with the definition of the named inst. When we check it, the error
% messages we generate for any errors in it will lack information
% about what nested sequence of named inst definitions the errors is
% inside. We therefore compute the error messages on the original
% inst as well.
%
% If ExpandedInstSpecs is nonempty, then UnexpandedInstSpecs should
% be nonempty as well, but we prepare for it to be empty just in case.
check_mutable_inst(ModuleInfo, ModuleParams, Context, DummyInstVarSet,
[], OrigInst, [], UnexpandedInstSpecs),
(
UnexpandedInstSpecs = [],
% Printing error messages without the proper context is better than
% not printing error messages at all, once we have discovered
% an error.
!:Specs = ExpandedInstSpecs ++ !.Specs
;
UnexpandedInstSpecs = [_ | _],
!:Specs = UnexpandedInstSpecs ++ !.Specs
)
).
%---------------------------------------------------------------------------%
% Add an error to !Specs for each part of the inst that isn't allowed
% inside a mutable declaration.
%
:- pred check_mutable_inst(module_info::in, module_params::in,
prog_context::in, inst_varset::in, list(inst_ctor)::in, mer_inst::in,
list(error_spec)::in, list(error_spec)::out) is det.
check_mutable_inst(ModuleInfo, ModuleParams, Context, InstVarSet,
ParentInsts, Inst, !Specs) :-
(
( Inst = any(Uniq, _)
; Inst = ground(Uniq, _)
),
check_mutable_inst_uniqueness(ModuleInfo, Context, InstVarSet,
ParentInsts, Inst, Uniq, !Specs)
;
Inst = bound(Uniq, _, BoundFunctors),
check_mutable_inst_uniqueness(ModuleInfo, Context, InstVarSet,
ParentInsts, Inst, Uniq, !Specs),
check_mutable_bound_functors(ModuleInfo, ModuleParams, Context,
InstVarSet, ParentInsts, BoundFunctors, !Specs)
;
Inst = defined_inst(InstName),
(
InstName = user_inst(UserInstSymName, UserInstArgs),
list.length(UserInstArgs, UserInstArity),
UserInstCtor = inst_ctor(UserInstSymName, UserInstArity),
( if
list.member(UserInstCtor, ParentInsts)
then
true
else if
UserInstSymName =
qualified(UserInstModuleName, UserInstBaseName),
UserInstModuleName = mercury_public_builtin_module,
UserInstArity = 0,
( UserInstBaseName = "dead"
; UserInstBaseName = "mostly_dead"
)
then
FreePieces =
color_as_incorrect([words("may not appear in")]) ++
[decl("mutable"), words("declarations.")],
UnqualInstName =
user_inst(unqualified(UserInstBaseName), UserInstArgs),
% There is special code in error_msg_inst.m to handle
% inst names from which the qualification by
% mercury_public_builtin_module has been stripped.
UnqualInst = defined_inst(UnqualInstName),
invalid_inst_in_mutable(ModuleInfo, Context, InstVarSet,
ParentInsts, UnqualInst, FreePieces, !Specs)
else
check_mutable_insts(ModuleInfo, ModuleParams, Context,
InstVarSet, ParentInsts, UserInstArgs, !Specs),
module_info_get_inst_table(ModuleInfo, InstTable),
inst_table_get_user_insts(InstTable, UserInstTable),
( if map.search(UserInstTable, UserInstCtor, InstDefn) then
InstDefn = hlds_inst_defn(DefnInstVarSet, _Params,
InstBody, _MMTC, _Context, _Status),
InstBody = eqv_inst(EqvInst),
DefnParentInsts = [UserInstCtor | ParentInsts],
check_mutable_inst(ModuleInfo, ModuleParams, Context,
DefnInstVarSet, DefnParentInsts, EqvInst, !Specs)
else
UndefInstCtors = ModuleParams ^ mp_undef_inst_ctors,
( if
set_tree234.contains(UndefInstCtors, UserInstCtor)
then
% An error message about UserInstCtor being undefined
% has already been generated. Any message we could
% generate here would only repeat the same information
% in a (possibly confusingly) different form.
true
else
% This should not happen, but just in case it does ...
UndefinedPieces =
color_as_incorrect([words("is not defined.")]),
invalid_inst_in_mutable(ModuleInfo, Context,
InstVarSet, ParentInsts, Inst, UndefinedPieces,
!Specs)
)
)
)
;
( InstName = unify_inst(_, _, _, _)
; InstName = merge_inst(_, _)
; InstName = ground_inst(_, _, _, _)
; InstName = any_inst(_, _, _, _)
; InstName = shared_inst(_)
; InstName = mostly_uniq_inst(_)
; InstName = typed_inst(_, _)
; InstName = typed_ground(_, _)
),
unexpected($pred, "non-user inst")
)
;
Inst = free,
FreePieces = color_as_incorrect([words("may not appear in")]) ++
[decl("mutable"), words("declarations.")],
invalid_inst_in_mutable(ModuleInfo, Context, InstVarSet, ParentInsts,
Inst, FreePieces, !Specs)
;
Inst = constrained_inst_vars(_, _),
ConstrainedPieces = [words("is constrained, and thus")] ++
color_as_incorrect([words("may not appear")]) ++
[words("in"), decl("mutable"), words("declarations.")],
invalid_inst_in_mutable(ModuleInfo, Context, InstVarSet, ParentInsts,
Inst, ConstrainedPieces, !Specs)
;
Inst = inst_var(_)
% The parser ensures that the inst in the mutable declaration does
% not have any variables. Any variables we encounter here must be
% a parameter from a named inst that the top level inst refers to
% either directly or indirectly.
;
Inst = not_reached,
unexpected($pred, "not_reached")
).
:- pred check_mutable_bound_functors(module_info::in, module_params::in,
prog_context::in, inst_varset::in,
list(inst_ctor)::in, list(bound_functor)::in,
list(error_spec)::in, list(error_spec)::out) is det.
check_mutable_bound_functors(_ModuleInfo, _ModuleParams, _Context,
_InstVarSet, _ParentInsts, [], !Specs).
check_mutable_bound_functors(ModuleInfo, ModuleParams, Context,
InstVarSet, ParentInsts, [BoundFunctor | BoundFunctors], !Specs) :-
BoundFunctor = bound_functor(_ConsId, ArgInsts),
check_mutable_insts(ModuleInfo, ModuleParams, Context, InstVarSet,
ParentInsts, ArgInsts, !Specs),
check_mutable_bound_functors(ModuleInfo, ModuleParams, Context, InstVarSet,
ParentInsts, BoundFunctors, !Specs).
:- pred check_mutable_insts(module_info::in, module_params::in,
prog_context::in, inst_varset::in, list(inst_ctor)::in, list(mer_inst)::in,
list(error_spec)::in, list(error_spec)::out) is det.
check_mutable_insts(_ModuleInfo, _ModuleParams, _Context, _InstVarSet,
_ParentInsts, [], !Specs).
check_mutable_insts(ModuleInfo, ModuleParams, Context, InstVarSet,
ParentInsts, [Inst | Insts], !Specs) :-
check_mutable_inst(ModuleInfo, ModuleParams, Context, InstVarSet,
ParentInsts, Inst, !Specs),
check_mutable_insts(ModuleInfo, ModuleParams, Context, InstVarSet,
ParentInsts, Insts, !Specs).
%---------------------%
:- pred check_mutable_inst_uniqueness(module_info::in, prog_context::in,
inst_varset::in, list(inst_ctor)::in, mer_inst::in, uniqueness::in,
list(error_spec)::in, list(error_spec)::out) is det.
check_mutable_inst_uniqueness(ModuleInfo, Context, InstVarSet, ParentInsts,
Inst, Uniq, !Specs) :-
(
Uniq = shared
;
(
Uniq = unique,
UniqStr = "unique"
;
Uniq = mostly_unique,
UniqStr = "mostly_unique"
;
Uniq = clobbered,
UniqStr = "clobbered"
;
Uniq = mostly_clobbered,
UniqStr = "mostly_clobbered"
),
( if Inst = ground(Uniq, _) then
UniqPieces = color_as_incorrect([words("may not appear")]) ++
[words("in"), decl("mutable"), words("declarations.")]
else
UniqPieces =
[words("has uniqueness"), quote(UniqStr), suffix(","),
words("which")] ++
color_as_incorrect([words("may not appear")]) ++
[words("in"), decl("mutable"), words("declarations.")]
),
invalid_inst_in_mutable(ModuleInfo, Context, InstVarSet, ParentInsts,
Inst, UniqPieces, !Specs)
).
% Note: the caller is responsible for coloring the relevant parts of
% ProblemPieces.
%
:- pred invalid_inst_in_mutable(module_info::in, prog_context::in,
inst_varset::in, list(inst_ctor)::in, mer_inst::in, list(format_piece)::in,
list(error_spec)::in, list(error_spec)::out) is det.
invalid_inst_in_mutable(ModuleInfo, Context, InstVarSet, ParentInsts, Inst,
ProblemPieces, !Specs) :-
named_parents_to_pieces(ParentInsts, ParentPieces),
InstPieces = error_msg_inst(ModuleInfo, InstVarSet,
do_not_expand_named_insts, uod_user, quote_short_inst, [], [],
[nl_indent_delta(1)], [nl_indent_delta(-1)], Inst),
Pieces = [words("Error:") | ParentPieces] ++
[words("the inst")] ++ color_as_subject(InstPieces) ++
ProblemPieces ++ [nl],
Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces),
!:Specs = [Spec | !.Specs].
:- pred named_parents_to_pieces(list(inst_ctor)::in,
list(format_piece)::out) is det.
named_parents_to_pieces([], []).
named_parents_to_pieces([InstCtor | InstCtors], Pieces) :-
named_parent_to_pieces(InstCtor, HeadPieces),
named_parents_to_pieces(InstCtors, TailPieces),
Pieces = HeadPieces ++ TailPieces.
:- pred named_parent_to_pieces(inst_ctor::in,
list(format_piece)::out) is det.
named_parent_to_pieces(InstCtor, Pieces) :-
InstCtor = inst_ctor(InstName, InstArity),
Pieces = [words("in the expansion of the named inst"),
qual_sym_name_arity(sym_name_arity(InstName, InstArity)),
suffix(":"), nl].
%---------------------------------------------------------------------------%
:- end_module hlds.make_hlds.add_mutable_aux_preds.
%---------------------------------------------------------------------------%