Files
mercury/compiler/module_qual.id_set.m
Julien Fischer 3066779c5c Fix spelling.
compiler/module_qual.id_set.m:
    As above.
2025-01-11 01:39:26 +11:00

589 lines
22 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2015-2016, 2019, 2021-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 defines the data structures we use to record
% what entities are available from which modules and with what permissions.
%
:- module parse_tree.module_qual.id_set.
:- interface.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.error_spec.
:- import_module parse_tree.module_qual.mq_info.
:- import_module parse_tree.module_qual.qual_errors.
:- import_module parse_tree.prog_data.
:- import_module list.
%---------------------------------------------------------------------------%
% We keep track of these kinds of entities.
:- type qual_id_kind
---> qual_id_type
; qual_id_inst
; qual_id_mode
; qual_id_class.
% This identifies an entity among other entities of the same kind.
:- type mq_id
---> mq_id(sym_name, int).
%---------------------------------------------------------------------------%
%
% We record two kinds of permissions for each entity:
%
% - whether it may be used in the interface, and
% - whether it may be used without full module qualification.
%
% An entity may be used in the interface if it is defined in the current
% module or one of its ancestors, or if it is defined in a module that is
% imported (or used) in the interface.
%
% An entity may be used without full module qualification if it is defined
% in the current module, in one of its ancestors, or in a module that is
% the subject of an explicit `import_module' declaration in the current module.
% It may not be used without full qualification if it is made available by
% a `use_module' declaration, or if it is defined in a `.opt' and `.trans_opt'
% file.
%
% The two are not independent: an entity may be usable in the interface
% only if fully qualified (if it is defined in a module that has a
% `use_module' declaration for it in the interface), while it may be
% usable in the implementation even if not fully qualified (if that defining
% module has an `import_module' declaration in the implementation.)
%
:- type module_permissions
---> module_permissions(
mp_in_int :: perm_in_int,
mp_in_imp :: perm_in_imp
).
:- type perm_in_int
---> may_not_use_in_int
; may_use_in_int(need_qualifier).
:- type perm_in_imp
---> may_use_in_imp(need_qualifier).
% When we process types, typeclasses, insts or modes, we need to know
% whether they occur in the interface of the current module. This is
% so that if we see e.g. m1.t1 in the interface, we can mark module m1
% as being used in the interface, so we can avoid generating a warning
% about m1 being unused in the interface.
%
:- type mq_in_interface
---> mq_not_used_in_interface
; mq_used_in_interface.
%---------------------------------------------------------------------------%
%
% An id_set represents the set of entities of a particular kind
% whose definitions we have seen so far, and which are therefore available
% to resolve any ambiguities in unqualified references.
%
% Modules don't have an arity, but for simplicity we use the same
% data structure for modules as for types etc, assigning arity zero
% to all module names.
%
:- type id_set.
:- type type_id_set == id_set.
:- type inst_id_set == id_set.
:- type mode_id_set == id_set.
:- type class_id_set == id_set.
:- type module_id_set == id_set.
%---------------------------------------------------------------------------%
%
% The operations on id_sets.
%
:- pred id_set_init(id_set::out) is det.
% Insert an mq_id into an id_set, aborting with an error if the
% mq_id is not module qualified.
%
:- pred id_set_insert(module_permissions::in, mq_id::in,
id_set::in, id_set::out) is det.
:- pred get_names_in_id_set(id_set::in, list(string)::out) is det.
% Find the unique match in the current name space for a given mq_id
% from a list of ids. If none exists, either because no match was found
% or multiple matches were found, report an error.
%
% This predicate assumes that type_ids, inst_ids, mode_ids and
% class_ids have the same representation.
%
:- pred find_unique_match(mq_in_interface::in, mq_error_context::in,
id_set::in, qual_id_kind::in, mq_id::in, sym_name::out,
mq_info::in, mq_info::out,
list(error_spec)::in, list(error_spec)::out) is det.
%---------------------------------------------------------------------------%
% Check whether the parent module was imported, given the name of a
% child (or grandchild, etc.) module occurring in that parent module.
%
:- pred parent_module_is_imported(mq_in_interface::in, module_id_set::in,
module_name::in, module_name::in) is semidet.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module recompilation.
:- import_module recompilation.item_types.
:- import_module recompilation.record_uses.
:- import_module assoc_list.
:- import_module bool.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module set.
:- import_module set_tree234.
%---------------------------------------------------------------------------%
% We want efficient retrieval of all the modules which define an id
% with a certain name and arity, and we want support for the generation
% of useful diagnostics for slightly-wrong references. We therefore implement
% each id_set as a three stage map from
%
% - first the base name of an entity,
% - and then its arity,
% - and then its module name,
%
% to the permissions for the sym_name_arity we can construct for these.
%
% Having these three stages is good for performance. The first stage
% does simple, fast comparisons but nevertheless drastically reduces
% the size of the remaining search space. The second and third stages are
% usually done in very small trees, which means that having to do comparisons
% on the relatively complex structure of module names (when compared to
% strings and ints) does not slow us down.
%
% The three stages also help with the construction of the useful diagnostics.
%
% Going through just the first two stages allows us to see which modules
% define an entity with the given base name and arity. This allows us
% diagnostics to point out e.g. matches for a name/arity pair that this
% module knows about (usually due to reading them in from .int2 files)
% that are not visible to the module being compiled due to missing imports.
%
% Going through just the first stage allows us to see which arities
% have definitions for the given base name. This allows diagnostics to
% point out e.g. how many arguments too few or too many a call has.
%
% Going through none of the stages allow us to see which names exist
% for this kind of entity. This allows us to print "did you mean" messages
% that list similar names that the programmer may have meant to use.
:- type id_set == map(string, map(arity, permissions_map)).
:- type permissions_map == map(module_name, module_permissions).
%---------------------------------------------------------------------------%
id_set_init(IdSet) :-
map.init(IdSet).
%---------------------------------------------------------------------------%
id_set_insert(Permissions, MQId, !IdSet) :-
MQId = mq_id(SymName, Arity),
(
SymName = unqualified(_),
unexpected($pred, "unqualified id")
;
SymName = qualified(ModuleName, BaseName),
% Most of the time, BaseName does not occur in !.IdSet.
% We therefore try the insertion first, and only if it fails
% do we update the existing entry that caused that failure.
FreshPermissionsMap = map.singleton(ModuleName, Permissions),
FreshSubMap = map.singleton(Arity, FreshPermissionsMap),
( if map.insert(BaseName, FreshSubMap, !IdSet) then
true
else
map.lookup(!.IdSet, BaseName, SubMap0),
( if map.search(SubMap0, Arity, PermissionsMap0) then
insert_into_permissions_map(Permissions, ModuleName,
PermissionsMap0, PermissionsMap),
map.det_update(Arity, PermissionsMap, SubMap0, SubMap),
map.det_update(BaseName, SubMap, !IdSet)
else
map.det_insert(Arity, FreshPermissionsMap, SubMap0, SubMap),
map.det_update(BaseName, SubMap, !IdSet)
)
)
).
:- pred insert_into_permissions_map(module_permissions::in, module_name::in,
permissions_map::in, permissions_map::out) is det.
insert_into_permissions_map(NewPermissions, ModuleName, !PermissionsMap) :-
( if map.search(!.PermissionsMap, ModuleName, OldPermissions) then
% Grant the permissions granted by either OldPermissions or
% NewPermissions.
OldPermissions = module_permissions(OldPermInt, OldPermImp),
NewPermissions = module_permissions(NewPermInt, NewPermImp),
(
OldPermInt = may_not_use_in_int,
PermInt = NewPermInt
;
OldPermInt = may_use_in_int(OldIntNeedQual),
(
NewPermInt = may_not_use_in_int,
PermInt = OldPermInt
;
NewPermInt = may_use_in_int(NewIntNeedQual),
need_qual_only_if_both(OldIntNeedQual, NewIntNeedQual,
IntNeedQual),
PermInt = may_use_in_int(IntNeedQual)
)
),
OldPermImp = may_use_in_imp(OldImpNeedQual),
NewPermImp = may_use_in_imp(NewImpNeedQual),
need_qual_only_if_both(OldImpNeedQual, NewImpNeedQual, ImpNeedQual),
PermImp = may_use_in_imp(ImpNeedQual),
% Update the entry only if it changed.
( if
PermInt = OldPermInt,
PermImp = OldPermImp
then
true
else
Permissions = module_permissions(PermInt, PermImp),
map.det_update(ModuleName, Permissions, !PermissionsMap)
)
else
map.det_insert(ModuleName, NewPermissions, !PermissionsMap)
).
:- pred need_qual_only_if_both(need_qualifier::in, need_qualifier::in,
need_qualifier::out) is det.
need_qual_only_if_both(NeedQualA, NeedQualB, NeedQual) :-
( if
NeedQualA = must_be_qualified,
NeedQualB = must_be_qualified
then
NeedQual = must_be_qualified
else
NeedQual = may_be_unqualified
).
%---------------------------------------------------------------------------%
get_names_in_id_set(IdSet, Names) :-
map.sorted_keys(IdSet, Names).
%---------------------------------------------------------------------------%
find_unique_match(InInt, ErrorContext, IdSet, IdType, Id0, SymName,
!Info, !Specs) :-
% Find all IDs which match the current id.
Id0 = mq_id(SymName0, Arity),
BaseName = unqualify_name(SymName0),
id_set_search_sym_arity(InInt, IdSet, SymName0, BaseName, Arity,
Matches, IntMismatches, QualMismatches),
(
Matches = [],
% No matches for this id.
MaybeUniqModuleName = no,
mq_info_get_should_report_errors(!.Info, ReportErrors),
(
ReportErrors = should_report_errors,
mq_info_record_undef_mq_id(IdType, Id0, !Info),
mq_info_get_this_module(!.Info, ThisModuleName),
id_set_search_sym(IdSet, SymName0, PossibleArities),
report_undefined_mq_id(!.Info, ErrorContext, Id0, IdType,
ThisModuleName, IntMismatches, QualMismatches,
PossibleArities, !Specs),
% If a module defines an entity that this module refers to,
% even without the required module qualification, then reporting
% that module as being unused would be wrong.
%
% This is so even if the correct definition of Id0 could have
% come from any one of several modules.
list.foldl(mq_info_set_module_used(InInt), QualMismatches, !Info)
;
ReportErrors = should_not_report_errors
)
;
Matches = [ModuleName],
% A unique match for this ID.
MaybeUniqModuleName = yes(ModuleName)
;
Matches = [_, _ | _],
MaybeUniqModuleName = no,
mq_info_get_should_report_errors(!.Info, ReportErrors),
(
ReportErrors = should_report_errors,
mq_info_record_undef_mq_id(IdType, Id0, !Info),
NonUsableModuleNames = IntMismatches ++ QualMismatches,
report_ambiguous_match(ErrorContext, Id0, IdType,
Matches, NonUsableModuleNames, !Specs)
;
ReportErrors = should_not_report_errors
)
),
(
MaybeUniqModuleName = no,
% Returning any SymName is fine, since it won't be used.
Id0 = mq_id(SymName, _)
;
MaybeUniqModuleName = yes(UniqModuleName),
SymName = qualified(UniqModuleName, BaseName),
mq_info_set_module_used(InInt, UniqModuleName, !Info),
UsedItemType = convert_used_item_type(IdType),
ItemName0 = recomp_item_name(SymName0, Arity),
ItemName = recomp_item_name(SymName, Arity),
update_recompilation_info(
record_used_item(UsedItemType, ItemName0, ItemName), !Info)
).
:- pred mq_info_record_undef_mq_id(qual_id_kind::in, mq_id::in,
mq_info::in, mq_info::out) is det.
mq_info_record_undef_mq_id(IdType, Id, !Info) :-
mq_info_get_suppress_found_undef(!.Info, SuppressFoundUndef),
(
SuppressFoundUndef = suppress_found_undef
;
SuppressFoundUndef = do_not_suppress_found_undef,
Id = mq_id(SymName, Arity),
(
IdType = qual_id_type,
TypeCtor = type_ctor(SymName, Arity),
mq_info_get_undef_types(!.Info, UndefTypes0),
set_tree234.insert(TypeCtor, UndefTypes0, UndefTypes),
mq_info_set_undef_types(UndefTypes, !Info)
;
IdType = qual_id_inst,
InstCtor = inst_ctor(SymName, Arity),
mq_info_get_undef_insts(!.Info, UndefInsts0),
set_tree234.insert(InstCtor, UndefInsts0, UndefInsts),
mq_info_set_undef_insts(UndefInsts, !Info)
;
IdType = qual_id_mode,
ModeCtor = mode_ctor(SymName, Arity),
mq_info_get_undef_modes(!.Info, UndefModes0),
set_tree234.insert(ModeCtor, UndefModes0, UndefModes),
mq_info_set_undef_modes(UndefModes, !Info)
;
IdType = qual_id_class,
SNA = sym_name_arity(SymName, Arity),
mq_info_get_undef_typeclasses(!.Info, UndefTypeclasses0),
set_tree234.insert(SNA, UndefTypeclasses0, UndefTypeclasses),
mq_info_set_undef_typeclasses(UndefTypeclasses, !Info)
)
).
:- func convert_used_item_type(qual_id_kind) = used_item_type.
convert_used_item_type(qual_id_type) = used_type_name.
convert_used_item_type(qual_id_inst) = used_inst.
convert_used_item_type(qual_id_mode) = used_mode.
convert_used_item_type(qual_id_class) = used_typeclass.
%---------------------------------------------------------------------------%
:- pred id_set_search_sym_arity(mq_in_interface::in, id_set::in,
sym_name::in, string::in, int::in, list(module_name)::out,
list(module_name)::out, list(module_name)::out) is det.
id_set_search_sym_arity(InInt, IdSet, SymName, UnqualName, Arity,
Matches, IntMismatches, QualMismatches) :-
( if
map.search(IdSet, UnqualName, SubMap),
map.search(SubMap, Arity, PermissionsMap)
then
find_matches_in_permissions_map(InInt, SymName, PermissionsMap,
Matches, IntMismatches, QualMismatches)
else
Matches = [],
IntMismatches = [],
QualMismatches = []
).
:- pred find_matches_in_permissions_map(mq_in_interface::in, sym_name::in,
permissions_map::in, list(module_name)::out,
list(module_name)::out, list(module_name)::out) is det.
find_matches_in_permissions_map(InInt, SymName, PermissionsMap,
Matches, IntMismatches, QualMismatches) :-
map.foldr3(add_matching_and_nearmiss_modules(InInt, SymName),
PermissionsMap, [], Matches, [], IntMismatches, [], QualMismatches).
:- pred add_matching_and_nearmiss_modules(mq_in_interface::in, sym_name::in,
module_name::in, module_permissions::in,
list(module_name)::in, list(module_name)::out,
list(module_name)::in, list(module_name)::out,
list(module_name)::in, list(module_name)::out) is det.
add_matching_and_nearmiss_modules(InInt, SymName, ModuleName, Permissions,
!Matches, !IntMismatches, !QualMismatches) :-
(
SymName = unqualified(_),
FullyModuleQualified = no,
add_matching_and_nearmiss_modules_int(InInt, FullyModuleQualified,
ModuleName, Permissions,
!Matches, !IntMismatches, !QualMismatches)
;
SymName = qualified(QualModuleName, _),
( if
partial_sym_name_matches_full(QualModuleName, ModuleName)
then
( if QualModuleName = ModuleName then
FullyModuleQualified = yes
else
FullyModuleQualified = no
),
add_matching_and_nearmiss_modules_int(InInt, FullyModuleQualified,
ModuleName, Permissions,
!Matches, !IntMismatches, !QualMismatches)
else if
ModuleNameComponents = sym_name_to_list(ModuleName),
QualModuleNameComponents = sym_name_to_list(QualModuleName),
list.sublist(QualModuleNameComponents, ModuleNameComponents)
then
% The missing module name components are not all at the start.
% XXX *Should* this be a problem?
!:QualMismatches = [ModuleName | !.QualMismatches]
else
true
)
).
:- pred add_matching_and_nearmiss_modules_int(mq_in_interface::in, bool::in,
module_name::in, module_permissions::in,
list(module_name)::in, list(module_name)::out,
list(module_name)::in, list(module_name)::out,
list(module_name)::in, list(module_name)::out) is det.
add_matching_and_nearmiss_modules_int(InInt, FullyModuleQualified,
ModuleName, Permissions, !Matches, !IntMismatches, !QualMismatches) :-
Permissions = module_permissions(PermInInt, PermInImp),
(
InInt = mq_used_in_interface,
(
PermInInt = may_not_use_in_int,
!:IntMismatches = [ModuleName | !.IntMismatches]
;
PermInInt = may_use_in_int(NeedQual),
add_matching_and_nearmiss_modules_qual(FullyModuleQualified,
NeedQual, ModuleName, !Matches, !QualMismatches)
)
;
InInt = mq_not_used_in_interface,
PermInImp = may_use_in_imp(NeedQual),
add_matching_and_nearmiss_modules_qual(FullyModuleQualified,
NeedQual, ModuleName, !Matches, !QualMismatches)
).
:- pred add_matching_and_nearmiss_modules_qual(bool::in, need_qualifier::in,
module_name::in,
list(module_name)::in, list(module_name)::out,
list(module_name)::in, list(module_name)::out) is det.
add_matching_and_nearmiss_modules_qual(FullyModuleQualified, NeedQual,
ModuleName, !Matches, !QualMismatches) :-
( if
( FullyModuleQualified = yes
; NeedQual = may_be_unqualified
)
then
!:Matches = [ModuleName | !.Matches]
else
!:QualMismatches = [ModuleName | !.QualMismatches]
).
%---------------------------------------------------------------------------%
:- pred id_set_search_sym(id_set::in, sym_name::in, set(int)::out) is det.
id_set_search_sym(IdSet, SymName, PossibleArities) :-
UnqualName = unqualify_name(SymName),
( if
map.search(IdSet, UnqualName, SubMap)
then
map.to_assoc_list(SubMap, SubMapPairs),
find_matching_arities(SymName, SubMapPairs, set.init, PossibleArities)
else
set.init(PossibleArities)
).
:- pred find_matching_arities(sym_name::in,
assoc_list(int, permissions_map)::in, set(int)::in, set(int)::out) is det.
find_matching_arities(_SymName, [], !PossibleArities).
find_matching_arities(SymName, [Pair | Pairs], !PossibleArities) :-
Pair = Arity - PermissionsMap,
find_matches_in_permissions_map(mq_not_used_in_interface, SymName,
PermissionsMap, Matches, IntMismatches, QualMismatches),
( if
( Matches = [_ | _]
; IntMismatches = [_ | _]
; QualMismatches = [_ | _]
)
then
set.insert(Arity, !PossibleArities)
else
true
),
find_matching_arities(SymName, Pairs, !PossibleArities).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
parent_module_is_imported(InInt, ModuleIdSet, ParentModule, ChildModule) :-
% Find the module name at the start of the ChildModule;
% this submodule will be a direct sub-module of ParentModule.
DirectSubModuleName = get_first_module_name(ChildModule),
% Check that the ParentModule was imported.
% We do this by looking up the definitions for the direct sub-module
% and checking that the one in ParentModule came from an
% imported module.
Arity = 0,
map.search(ModuleIdSet, DirectSubModuleName, SubMap),
map.search(SubMap, Arity, PermissionsMap),
map.search(PermissionsMap, ParentModule, ParentModulePermissions),
ParentModulePermissions = module_permissions(PermInInt, PermInImp),
(
InInt = mq_used_in_interface,
PermInInt = may_use_in_int(may_be_unqualified)
;
InInt = mq_not_used_in_interface,
PermInImp = may_use_in_imp(may_be_unqualified)
).
% Given a module name, possibly module-qualified, return the name
% of the first module in the qualifier list. For example, given
% `foo.bar.baz', this returns `foo', and given just `baz',
% it returns `baz'.
%
:- func get_first_module_name(module_name) = string.
get_first_module_name(unqualified(ModuleName)) = ModuleName.
get_first_module_name(qualified(Parent, _)) = get_first_module_name(Parent).
%---------------------------------------------------------------------------%
:- end_module parse_tree.module_qual.id_set.
%---------------------------------------------------------------------------%