Files
mercury/compiler/check_import_accessibility.m
Julien Fischer b9777da30f Fix some documentation errors in the compiler.
Fix some mispelled variable names.

compiler/accumulator.m:
compiler/check_import_accessibility.m:
compiler/decide_type_repn.m:
compiler/du_type_layout.m:
compiler/error_spec.m:
compiler/hlds_data.m:
compiler/hlds_goal.m:
compiler/mlds.m:
compiler/parse_item.m:
compiler/prog_data.m:
compiler/type_util.m:
    As above.
2026-01-24 01:19:51 +11:00

1033 lines
44 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2023-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: check_import_accessibility.m.
% Main author: fjh (original), zs (current).
%
% Given the parse tree of a module, and information about the accessibility
% of included and/or imported modules gathered by our caller from either
% an aug_compilation_unit or an aug_make_int_unit, we look for and report
% two different but related kinds of errors.
%
% The first is when we see a reference to module x.y.z, but module x.y
% does not include a submodule named z.
%
% The second is when module m.n has an import_module or use_module
% declaration for module x.y.z, but there is some ancestor of x.y.z
% (either x or x.y) that neither m.n nor its ancestor m imports or uses.
%
% XXX ITEM_LIST We should either record in an updated AugCompUnit
% the set of imported modules that are inaccessible, or remove their
% imports from it, so that
%
% - when we report e.g. an undefined type, we don't tell the user that
% the module that defines the type hasn't been imported, when in fact
% it *was* imported, but the import was disregarded because the module
% is inaccessible due to the missing import of an ancestor; and
%
% - we don't generate "unused module" warnings for them when
% --warn-unused-imports is enabled.
%
%---------------------------------------------------------------------------%
:- module parse_tree.check_import_accessibility.
:- interface.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.error_spec.
:- import_module parse_tree.prog_parse_tree.
:- import_module list.
%---------------------------------------------------------------------------%
:- type import_accessibility_info.
% Generate the information that the main part of this module,
% check_import_accessibility, needs.
%
% This part needs to be done slightly differently for
% aug_compilation_units and aug_make_int_units, which is why
% this part of the job is separated out.
%
:- pred aug_comp_unit_get_import_accessibility_info(aug_compilation_unit::in,
import_accessibility_info::out) is det.
:- pred aug_make_int_unit_get_import_accessibility_info(aug_make_int_unit::in,
import_accessibility_info::out) is det.
%---------------------------------------------------------------------------%
% check_import_accessibility(ParseTreeModuleSrc, ImportAccessibilityInfo,
% MissingModulesInt, MissingModulesImp, DelayedSpecs, Specs):
%
% This predicate does the job described in the top-of-module comment.
% It also returns the list of modules for which at least use_module,
% if not import_module, declarations are missing from the interface
% and implementation sections respectively. Any errors caused by these
% are returned in DelayedSpecs, not Specs.
%
:- pred check_import_accessibility(parse_tree_module_src::in,
import_accessibility_info::in,
list(module_name)::out, list(module_name)::out,
list(error_spec)::out, list(error_spec)::out) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module libs.
:- import_module libs.globals.
:- import_module parse_tree.convert_import_use.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_data_foreign.
:- import_module parse_tree.prog_item.
:- import_module map.
:- import_module maybe.
:- import_module one_or_more.
:- import_module require.
:- import_module set.
:- import_module term_context.
%---------------------------------------------------------------------------%
% aug_{comp,make_int}_unit_get_import_accessibility_info both generate
% a value of type import_accessibility_info by scanning the relevant kinds
% of items in the input structures they are given.
%
% - The iai_read_modules field will contain the set of module names
% from whose files (source files, interface files, optimization files)
% the information in the rest of the fields originates.
%
% - The iai_inclusion_map field will map the name of each module
% that is named in an include_module declaration in the input
% data structure to the context of that declaration.
%
% The iai_seen_includes field, supplied by the caller, specifies
% whether any include_module declarations in the read-in modules
% may be intentionally missing from this map. When the input is
% an aug_compilation_unit, all include_module declarations in the
% relevant modules will be in this map, but when the input is
% an aug_make_int_unit, the map will contain information only about
% include_module declarations in the interface sections of those modules
% (since the .int3 files we read in from them contain nothing at all
% from the implementation section).
%
% - The iai_src_int_import_use_map field will map the module names
% that occur in import_module or use_module declarations in the
% interface sections of the given parse_tree_module_src itself
% to the context(s) of those declarations.
%
% - The iai_src_imp_import_use_map field contains the same info
% for the implementation section.
%
% - The iai_ancestor_import_use_map field again contains the same info,
% but for import_module and use_module declarations read from
% (the .int0 interface files of) the ancestors of the
% parse_tree_module_src.
%
% NOTE By making the value in both the module_inclusion_map and the
% module_import_or_use_map a (nonempty) list, we can represent situations
% in which a module includes, imports or uses another module
% more than once. This is an error, and we could and probably should
% diagnose it here, but doing so would require disabling the code
% we have elsewhere in the compiler that does that job. If we did that,
% we could replace the nonempty lists of contexts with just one context,
% and a message for every other context.
%
:- type import_accessibility_info
---> import_accessibility_info(
iai_read_modules :: set(module_name),
iai_seen_includes :: seen_includes,
iai_inclusion_map :: module_inclusion_map,
iai_src_int_import_use_map :: module_import_or_use_map,
iai_src_imp_import_use_map :: module_import_or_use_map,
iai_ancestor_import_use_map :: module_import_or_use_map
).
:- type seen_includes
---> seen_only_int_includes
; seen_all_includes.
% The module_inclusion_map and module_import_or_use_map are computed by
% record_includes_imports_uses, for use by find_any_missing_ancestor_imports.
% For their documentation, see those predicates below.
:- type maybe_abstract_section
---> non_abstract_section
; abstract_section.
:- type include_context
---> include_context(maybe_abstract_section, term_context).
:- type module_inclusion_map ==
map(module_name, one_or_more(include_context)).
:- type import_or_use_context
---> import_or_use_context(import_or_use, term_context).
:- type module_import_or_use_map ==
map(module_name, one_or_more(import_or_use_context)).
%---------------------------------------------------------------------------%
aug_comp_unit_get_import_accessibility_info(AugCompUnit,
ImportAccessibilityInfo) :-
AugCompUnit = aug_compilation_unit(ParseTreeModuleSrc, AncestorIntSpecs,
DirectIntSpecs, IndirectIntSpecs, PlainOpts, _TransOpts,
IntForOptSpecs, _TypeRepnSpecs, _ModuleVersionNumbers),
some [!ReadModules, !InclMap, !SrcIntImportUseMap, !SrcImpImportUseMap,
!AncestorImportUseMap]
(
set.init(!:ReadModules),
map.init(!:InclMap),
map.init(!:SrcIntImportUseMap),
map.init(!:SrcImpImportUseMap),
map.init(!:AncestorImportUseMap),
ModuleName = ParseTreeModuleSrc ^ ptms_module_name,
Ancestors = get_ancestors_set(ModuleName),
record_includes_imports_uses_in_parse_tree_module_src(
ParseTreeModuleSrc,
!ReadModules, !InclMap, !SrcIntImportUseMap, !SrcImpImportUseMap),
map.foldl3_values(
record_includes_imports_uses_in_ancestor_int_spec(Ancestors),
AncestorIntSpecs, !ReadModules, !InclMap, !AncestorImportUseMap),
map.foldl2_values(
record_includes_imports_uses_in_direct_int1_spec(Ancestors),
DirectIntSpecs, !ReadModules, !InclMap),
map.foldl2_values(
record_includes_imports_uses_in_indirect_int2_spec(Ancestors),
IndirectIntSpecs, !ReadModules, !InclMap),
map.foldl_values(
record_includes_imports_uses_in_parse_tree_plain_opt(Ancestors),
PlainOpts, !AncestorImportUseMap),
% .trans_opt files may contain no include_module, import_module
% or use_module declarations, so there is nothing to record for them.
map.foldl3_values(
record_includes_imports_uses_in_int_for_opt_spec(Ancestors),
IntForOptSpecs, !ReadModules, !InclMap, !AncestorImportUseMap),
ImportAccessibilityInfo = import_accessibility_info(!.ReadModules,
seen_all_includes, !.InclMap,
!.SrcIntImportUseMap, !.SrcImpImportUseMap, !.AncestorImportUseMap)
).
aug_make_int_unit_get_import_accessibility_info(AugMakeIntUnit,
ImportAccessibilityInfo) :-
AugMakeIntUnit = aug_make_int_unit(ParseTreeModuleSrc, _, AncestorIntSpecs,
DirectIntSpecs, IndirectIntSpecs, _ModuleVersionNumbers),
some [!ReadModules, !InclMap, !SrcIntImportUseMap, !SrcImpImportUseMap,
!AncestorImportUseMap]
(
set.init(!:ReadModules),
map.init(!:InclMap),
map.init(!:SrcIntImportUseMap),
map.init(!:SrcImpImportUseMap),
map.init(!:AncestorImportUseMap),
ModuleName = ParseTreeModuleSrc ^ ptms_module_name,
Ancestors = get_ancestors_set(ModuleName),
record_includes_imports_uses_in_parse_tree_module_src(
ParseTreeModuleSrc,
!ReadModules, !InclMap, !SrcIntImportUseMap, !SrcImpImportUseMap),
map.foldl3_values(
record_includes_imports_uses_in_parse_tree_int0(Ancestors),
AncestorIntSpecs, !ReadModules, !InclMap, !AncestorImportUseMap),
map.foldl2_values(
record_includes_imports_uses_in_direct_int3_spec(Ancestors),
DirectIntSpecs, !ReadModules, !InclMap),
map.foldl2_values(
record_includes_imports_uses_in_indirect_int3_spec(Ancestors),
IndirectIntSpecs, !ReadModules, !InclMap),
ImportAccessibilityInfo = import_accessibility_info(!.ReadModules,
seen_only_int_includes, !.InclMap,
!.SrcIntImportUseMap, !.SrcImpImportUseMap, !.AncestorImportUseMap)
).
%---------------------------------------------------------------------------%
:- pred record_includes_imports_uses_in_parse_tree_module_src(
parse_tree_module_src::in,
set(module_name)::in, set(module_name)::out,
module_inclusion_map::in, module_inclusion_map::out,
module_import_or_use_map::in, module_import_or_use_map::out,
module_import_or_use_map::in, module_import_or_use_map::out) is det.
record_includes_imports_uses_in_parse_tree_module_src(ParseTreeModuleSrc,
!ReadModules, !MaybeAbstractInclMap,
!SrcIntImportUseMap, !SrcImpImportUseMap) :-
ParseTreeModuleSrc = parse_tree_module_src(ModuleName, _,
InclMap, ImportUseMap,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _),
set.insert(ModuleName, !ReadModules),
map.foldl(record_include(non_abstract_section, yes(non_abstract_section)),
InclMap, !MaybeAbstractInclMap),
map.foldl2(record_avail_in_import_use_map_entry, ImportUseMap,
!SrcIntImportUseMap, !SrcImpImportUseMap).
:- pred record_includes_imports_uses_in_ancestor_int_spec(set(module_name)::in,
ancestor_int_spec::in,
set(module_name)::in, set(module_name)::out,
module_inclusion_map::in, module_inclusion_map::out,
module_import_or_use_map::in, module_import_or_use_map::out) is det.
record_includes_imports_uses_in_ancestor_int_spec(Ancestors,
AncestorSpec, !ReadModules, !InclMap, !AncestorImportUseMap) :-
AncestorSpec = ancestor_int0(ParseTreeInt0, _ReadWhyInt0),
record_includes_imports_uses_in_parse_tree_int0(Ancestors,
ParseTreeInt0, !ReadModules, !InclMap, !AncestorImportUseMap).
:- pred record_includes_imports_uses_in_direct_int1_spec(
set(module_name)::in, direct_int1_spec::in,
set(module_name)::in, set(module_name)::out,
module_inclusion_map::in, module_inclusion_map::out) is det.
record_includes_imports_uses_in_direct_int1_spec(Ancestors,
DirectSpec, !ReadModules, !InclMap) :-
DirectSpec = direct_int1(ParseTreeInt1, ReadWhyInt1),
record_includes_imports_uses_in_parse_tree_int1(Ancestors,
ParseTreeInt1, ReadWhyInt1, !ReadModules, !InclMap).
:- pred record_includes_imports_uses_in_indirect_int2_spec(
set(module_name)::in, indirect_int2_spec::in,
set(module_name)::in, set(module_name)::out,
module_inclusion_map::in, module_inclusion_map::out) is det.
record_includes_imports_uses_in_indirect_int2_spec(Ancestors,
IndirectSpec, !ReadModules, !InclMap) :-
IndirectSpec = indirect_int2(ParseTreeInt2, ReadWhyInt2),
record_includes_imports_uses_in_parse_tree_int2(Ancestors,
ParseTreeInt2, ReadWhyInt2, !ReadModules, !InclMap).
:- pred record_includes_imports_uses_in_direct_int3_spec(
set(module_name)::in, direct_int3_spec::in,
set(module_name)::in, set(module_name)::out,
module_inclusion_map::in, module_inclusion_map::out) is det.
record_includes_imports_uses_in_direct_int3_spec(Ancestors,
IndirectSpec, !ReadModules, !InclMap) :-
IndirectSpec = direct_int3(ParseTreeInt3, _ReadWhyInt3),
record_includes_imports_uses_in_parse_tree_int3(Ancestors,
ParseTreeInt3, non_abstract_section, !ReadModules, !InclMap).
:- pred record_includes_imports_uses_in_indirect_int3_spec(
set(module_name)::in, indirect_int3_spec::in,
set(module_name)::in, set(module_name)::out,
module_inclusion_map::in, module_inclusion_map::out) is det.
record_includes_imports_uses_in_indirect_int3_spec(Ancestors,
IndirectSpec, !ReadModules, !InclMap) :-
IndirectSpec = indirect_int3(ParseTreeInt3, _ReadWhyInt3),
record_includes_imports_uses_in_parse_tree_int3(Ancestors,
ParseTreeInt3, abstract_section, !ReadModules, !InclMap).
:- pred record_includes_imports_uses_in_int_for_opt_spec(set(module_name)::in,
int_for_opt_spec::in,
set(module_name)::in, set(module_name)::out,
module_inclusion_map::in, module_inclusion_map::out,
module_import_or_use_map::in, module_import_or_use_map::out) is det.
record_includes_imports_uses_in_int_for_opt_spec(Ancestors,
IntForOptSpec, !ReadModules, !InclMap, !AncestorImportUseMap) :-
(
IntForOptSpec = for_opt_int0(ParseTreeInt0, _ReadWhyInt0),
record_includes_imports_uses_in_parse_tree_int0(Ancestors,
ParseTreeInt0, !ReadModules, !InclMap, !AncestorImportUseMap)
;
IntForOptSpec = for_opt_int1(ParseTreeInt1, ReadWhyInt1),
record_includes_imports_uses_in_parse_tree_int1(Ancestors,
ParseTreeInt1, ReadWhyInt1, !ReadModules, !InclMap)
;
IntForOptSpec = for_opt_int2(ParseTreeInt2, ReadWhyInt2),
record_includes_imports_uses_in_parse_tree_int2(Ancestors,
ParseTreeInt2, ReadWhyInt2, !ReadModules, !InclMap)
).
%---------------------%
:- pred record_includes_imports_uses_in_parse_tree_int0(set(module_name)::in,
parse_tree_int0::in,
set(module_name)::in, set(module_name)::out,
module_inclusion_map::in, module_inclusion_map::out,
module_import_or_use_map::in, module_import_or_use_map::out) is det.
record_includes_imports_uses_in_parse_tree_int0(Ancestors,
ParseTreeInt0, !ReadModules, !MaybeAbstractInclMap,
!AncestorImportUseMap) :-
ParseTreeInt0 = parse_tree_int0(ModuleName, _, _, InclMap, ImportUseMap,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _),
set.insert(ModuleName, !ReadModules),
% Both possible values of ReadWhyInt0 call for treating the file contents
% as non-abstract.
map.foldl(record_include(non_abstract_section, yes(non_abstract_section)),
InclMap, !MaybeAbstractInclMap),
( if set.contains(Ancestors, ModuleName) then
% XXX CLEANUP This work could be done on ImportUseMap,
% *without* constructing AllAvails.
section_import_and_or_use_map_to_item_avails(ImportUseMap,
IntAvails, ImpAvails),
AllAvails = IntAvails ++ ImpAvails,
record_avails_acc(AllAvails, !AncestorImportUseMap)
else
true
).
:- pred record_includes_imports_uses_in_parse_tree_int1(set(module_name)::in,
parse_tree_int1::in, read_why_int1::in,
set(module_name)::in, set(module_name)::out,
module_inclusion_map::in, module_inclusion_map::out) is det.
record_includes_imports_uses_in_parse_tree_int1(Ancestors,
ParseTreeInt1, ReadWhyInt1, !ReadModules, !InclMap) :-
ParseTreeInt1 = parse_tree_int1(ModuleName, _, _, InclMap, _,
_, _, _, _, _, _, _, _, _, _, _, _, _, _),
set.insert(ModuleName, !ReadModules),
(
( ReadWhyInt1 = rwi1_int_import
; ReadWhyInt1 = rwi1_imp_import
; ReadWhyInt1 = rwi1_int_use
; ReadWhyInt1 = rwi1_imp_use
; ReadWhyInt1 = rwi1_int_use_imp_import
),
% All these values of ReadWhyInt1 call for treating
% - the interface as non-abstract, and
% - the implementation as abstract.
map.foldl(
record_include(non_abstract_section, yes(abstract_section)),
InclMap, !InclMap)
;
ReadWhyInt1 = rwi1_opt,
map.foldl(
record_include(non_abstract_section, yes(non_abstract_section)),
InclMap, !InclMap)
;
ReadWhyInt1 = rwi1_type_repn
),
expect_not(set.contains(Ancestors, ModuleName), $pred,
"processing the .int file of an ancestor").
:- pred record_includes_imports_uses_in_parse_tree_int2(set(module_name)::in,
parse_tree_int2::in, read_why_int2::in,
set(module_name)::in, set(module_name)::out,
module_inclusion_map::in, module_inclusion_map::out) is det.
record_includes_imports_uses_in_parse_tree_int2(Ancestors,
ParseTreeInt2, ReadWhyInt2, !ReadModules, !InclMap) :-
ParseTreeInt2 = parse_tree_int2(ModuleName, _, _,
IntInclMap, _, _, _, _, _, _, _, _, _),
set.insert(ModuleName, !ReadModules),
InclMap = coerce(IntInclMap),
(
( ReadWhyInt2 = rwi2_int_use
; ReadWhyInt2 = rwi2_imp_use
; ReadWhyInt2 = rwi2_opt
),
map.foldl(record_include(non_abstract_section, no), InclMap, !InclMap)
;
ReadWhyInt2 = rwi2_abstract,
map.foldl(record_include(abstract_section, no), InclMap, !InclMap)
),
expect_not(set.contains(Ancestors, ModuleName), $pred,
"processing the .int2 file of an ancestor").
:- pred record_includes_imports_uses_in_parse_tree_int3(set(module_name)::in,
parse_tree_int3::in, maybe_abstract_section::in,
set(module_name)::in, set(module_name)::out,
module_inclusion_map::in, module_inclusion_map::out) is det.
record_includes_imports_uses_in_parse_tree_int3(Ancestors,
ParseTreeInt3, MaybeAbstractSection, !ReadModules, !InclMap) :-
ParseTreeInt3 = parse_tree_int3(ModuleName, _, IntInclMap,
_, _, _, _, _, _, _),
set.insert(ModuleName, !ReadModules),
InclMap = coerce(IntInclMap),
map.foldl(record_include(MaybeAbstractSection, no), InclMap, !InclMap),
expect_not(set.contains(Ancestors, ModuleName), $pred,
"processing the .int3 file of an ancestor").
:- pred record_includes_imports_uses_in_parse_tree_plain_opt(
set(module_name)::in, parse_tree_plain_opt::in,
module_import_or_use_map::in, module_import_or_use_map::out) is det.
record_includes_imports_uses_in_parse_tree_plain_opt(Ancestors,
ParseTreePlainOpt, !AncestorImportUseMap) :-
ParseTreePlainOpt = parse_tree_plain_opt(ModuleName, _, UseMap,
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _),
( if set.contains(Ancestors, ModuleName) then
Avails = use_map_to_item_avails(UseMap),
record_avails_acc(Avails, !AncestorImportUseMap)
else
true
).
%---------------------------------------------------------------------------%
:- pred record_include(maybe_abstract_section::in,
maybe(maybe_abstract_section)::in,
module_name::in, include_module_info::in,
module_inclusion_map::in, module_inclusion_map::out) is det.
record_include(MaybeAbsSectionInt, MaybeMaybeAbsSectionImp,
ModuleName, InclInfo, !InclMap) :-
InclInfo = include_module_info(Section, Context),
(
Section = ms_interface,
IncludeContext0 = include_context(MaybeAbsSectionInt, Context),
MaybeIncludeContext = yes(IncludeContext0)
;
Section = ms_implementation,
(
MaybeMaybeAbsSectionImp = yes(MaybeAbsSectionImp),
IncludeContext0 = include_context(MaybeAbsSectionImp, Context),
MaybeIncludeContext = yes(IncludeContext0)
;
MaybeMaybeAbsSectionImp = no,
MaybeIncludeContext = no
)
),
(
MaybeIncludeContext = yes(IncludeContext),
( if map.search(!.InclMap, ModuleName, OneOrMore0) then
OneOrMore0 = one_or_more(HeadContext, TailContexts),
OneOrMore =
one_or_more(IncludeContext, [HeadContext | TailContexts]),
map.det_update(ModuleName, OneOrMore, !InclMap)
else
OneOrMore = one_or_more(IncludeContext, []),
map.det_insert(ModuleName, OneOrMore, !InclMap)
)
;
MaybeIncludeContext = no
).
%---------------------%
:- pred record_avail_in_import_use_map_entry(module_name::in,
maybe_implicit_import_and_or_use::in,
module_import_or_use_map::in, module_import_or_use_map::out,
module_import_or_use_map::in, module_import_or_use_map::out) is det.
record_avail_in_import_use_map_entry(ModuleName, MaybeImplicit,
!IntImportUseMap, !ImpImportUseMap) :-
(
MaybeImplicit = implicit_avail(_ImplicitImportOrUse,
MaybeSectionImportOrUse),
(
MaybeSectionImportOrUse = no
;
MaybeSectionImportOrUse = yes(SectionImportOrUse),
record_avail_in_section(ModuleName, SectionImportOrUse,
!IntImportUseMap, !ImpImportUseMap)
)
;
MaybeImplicit = explicit_avail(SectionImportOrUse),
record_avail_in_section(ModuleName, SectionImportOrUse,
!IntImportUseMap, !ImpImportUseMap)
).
:- pred record_avail_in_section(module_name::in, section_import_and_or_use::in,
module_import_or_use_map::in, module_import_or_use_map::out,
module_import_or_use_map::in, module_import_or_use_map::out) is det.
record_avail_in_section(ModuleName, SectionImportOrUse,
!IntImportUseMap, !ImpImportUseMap) :-
(
( SectionImportOrUse = int_import(Context), IoU = import_decl
; SectionImportOrUse = int_use(Context), IoU = use_decl
),
record_avail(ModuleName, IoU, Context, !IntImportUseMap)
;
( SectionImportOrUse = imp_import(Context), IoU = import_decl
; SectionImportOrUse = imp_use(Context), IoU = use_decl
),
record_avail(ModuleName, IoU, Context, !ImpImportUseMap)
;
SectionImportOrUse = int_use_imp_import(IntContext, ImpContext),
record_avail(ModuleName, use_decl, IntContext, !IntImportUseMap),
record_avail(ModuleName, import_decl, ImpContext, !ImpImportUseMap)
).
:- pred record_avail(module_name::in, import_or_use::in, prog_context::in,
module_import_or_use_map::in, module_import_or_use_map::out) is det.
record_avail(ModuleName, ImportOrUse, Context, !ImportUseMap) :-
IoUC = import_or_use_context(ImportOrUse, Context),
( if map.search(!.ImportUseMap, ModuleName, OneOrMore0) then
OneOrMore0 = one_or_more(HeadIoUC, TailIoUCs),
OneOrMore = one_or_more(IoUC, [HeadIoUC | TailIoUCs]),
map.det_update(ModuleName, OneOrMore, !ImportUseMap)
else
OneOrMore = one_or_more(IoUC, []),
map.det_insert(ModuleName, OneOrMore, !ImportUseMap)
).
%---------------------%
:- pred record_avails_acc(list(item_avail)::in,
module_import_or_use_map::in, module_import_or_use_map::out) is det.
record_avails_acc([], !ImportUseMap).
record_avails_acc([Avail | Avails], !ImportUseMap) :-
(
Avail = avail_import(avail_import_info(ModuleName, Context, _SeqNum)),
ImportOrUse = import_decl
;
Avail = avail_use(avail_use_info(ModuleName, Context, _SeqNum)),
ImportOrUse = use_decl
),
IoUC = import_or_use_context(ImportOrUse, Context),
( if map.search(!.ImportUseMap, ModuleName, OneOrMore0) then
OneOrMore0 = one_or_more(HeadIoUC, TailIoUCs),
OneOrMore = one_or_more(IoUC, [HeadIoUC | TailIoUCs]),
map.det_update(ModuleName, OneOrMore, !ImportUseMap)
else
OneOrMore = one_or_more(IoUC, []),
map.det_insert(ModuleName, OneOrMore, !ImportUseMap)
),
record_avails_acc(Avails, !ImportUseMap).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
check_import_accessibility(ParseTreeModuleSrc, ImportAccessibilityInfo,
MissingModulesInt, MissingModulesImp, !:DelayedSpecs, !:Specs) :-
ModuleName = ParseTreeModuleSrc ^ ptms_module_name,
ModuleNameContext = ParseTreeModuleSrc ^ ptms_module_name_context,
ImportAccessibilityInfo = import_accessibility_info(ReadModules,
SeenIncludes, InclMap, SrcIntImportUseMap, SrcImpImportUseMap,
AncestorImportUseMap),
!:Specs = [],
% The current module is not an import, but this is the obvious place
% to check whether its purported parent module (if any) actually
% includes it.
report_any_missing_includes(ReadModules, SeenIncludes, InclMap,
ModuleName, [ModuleNameContext], !Specs),
map.foldl(
report_any_missing_includes_for_imports(ReadModules,
SeenIncludes, InclMap),
SrcIntImportUseMap, !Specs),
map.foldl(
report_any_missing_includes_for_imports(ReadModules,
SeenIncludes, InclMap),
SrcImpImportUseMap, !Specs),
% When checking whether avail declarations (i.e. import_module
% and use_module declarations) in the interface section
% have an accessible avail declaration for their ancestor modules,
% the places where those declarations may occur include not just
% the interface of the module itself, but also the contents of
% the .int0 interface files of ancestor modules.
map.union(append_one_or_more, SrcIntImportUseMap, AncestorImportUseMap,
SrcIntAncImportUseMap),
map.foldl(
find_any_missing_ancestor_imports(ModuleName, poa_parent,
SrcIntAncImportUseMap),
SrcIntImportUseMap, map.init, SrcIntMissingAncestorMap),
% When checking whether avail declarations in the implementation section
% have an accessible avail declaration for their ancestor modules,
% the places where those declarations may occur include not just
% the implementation section of the module itself, but also every place
% that the interface of the module has access to.
map.union(append_one_or_more, SrcIntAncImportUseMap, SrcImpImportUseMap,
SrcIntImpImportUseMap),
map.foldl(
find_any_missing_ancestor_imports(ModuleName, poa_parent,
SrcIntImpImportUseMap),
SrcImpImportUseMap, map.init, SrcImpMissingAncestorMap0),
% If we generate a message about a missing import (or use) for a module
% in the interface section, do not generate another message for it
% also missing in the implementation section, because adding an
% import_module or use_module declaration for it to the interface
% will also cure the problem in the implementation section.
map.keys(SrcIntMissingAncestorMap, SrcIntMissingAncestors),
map.delete_list(SrcIntMissingAncestors,
SrcImpMissingAncestorMap0, SrcImpMissingAncestorMap),
!:DelayedSpecs = [],
map.foldl2(
report_missing_ancestor(ModuleName,
missing_in_src_int(SrcImpImportUseMap)),
SrcIntMissingAncestorMap, [], MissingModulesInt, !DelayedSpecs),
map.foldl2(
report_missing_ancestor(ModuleName, missing_in_src_imp),
SrcImpMissingAncestorMap, [], MissingModulesImp, !DelayedSpecs).
%---------------------------------------------------------------------------%
:- type parent_or_ancestor
---> poa_parent
; poa_ancestor.
:- type import_and_or_use
---> import_only
; use_only
; import_and_use.
:- type missing_ancestor_info
---> missing_ancestor_info(
mai_modules :: set(module_name),
mai_max_depth :: parent_or_ancestor,
mai_import_use :: import_and_or_use,
mai_least_context :: term_context
).
:- type missing_ancestor_map == map(module_name, missing_ancestor_info).
% find_any_missing_ancestor_imports(CurrentModule, ParentOrAncestor,
% ImportUseMap, ImportedModule, IoUCs, !MissingAncestorMap):
%
% If there are any ancestors of ImportedModule for which there is
% neither an explicit import_module or use_module declaration in
% ImportUseMap, nor an implicit declaration by virtue of that ancestor
% module being an ancestor of CurrentModule as well, then record
% the fact that we are missing an import or use of that ancestor.
%
% We don't generate an error message right here, so that if several
% imported modules are missing the same ancestor, we can generate
% just one message for that missing ancestor.
%
% The other inputs allow us to record information that will make
% the eventual error message more informative.
%
:- pred find_any_missing_ancestor_imports(module_name::in,
parent_or_ancestor::in, module_import_or_use_map::in,
module_name::in, one_or_more(import_or_use_context)::in,
missing_ancestor_map::in, missing_ancestor_map::out) is det.
find_any_missing_ancestor_imports(CurrentModule, ParentOrAncestor,
ImportUseMap, ImportedModule, IoUCs, !MissingAncestorMap) :-
(
ImportedModule = qualified(ParentModule, _SubModule),
( if
(
% Does CurrentModule import ParentModule explicitly?
map.search(ImportUseMap, ParentModule, _ParentIoUCs)
;
% Is ParentModule the same as CurrentModule, or a parent
% or an ancestor of CurrentModule? If yes, then CurrentModule
% imports it implicitly.
is_same_module_or_submodule(CurrentModule, ParentModule)
)
then
true
else
IoUCs = one_or_more(HeadIoUC, TailIoUCs),
( if
map.search(!.MissingAncestorMap, ParentModule,
MissingAncestorInfo0)
then
MissingAncestorInfo0 = missing_ancestor_info(ChildModules0,
PoA0, ImportAndOrUse0, LeastContext0),
set.insert(ImportedModule, ChildModules0, ChildModules),
( if
PoA0 = poa_parent,
ParentOrAncestor = poa_ancestor
then
PoA = poa_ancestor
else
PoA = PoA0
),
update_iu_and_least_context(HeadIoUC,
ImportAndOrUse0, ImportAndOrUse1,
LeastContext0, LeastContext1),
list.foldl2(update_iu_and_least_context, TailIoUCs,
ImportAndOrUse1, ImportAndOrUse,
LeastContext1, LeastContext),
MissingAncestorInfo = missing_ancestor_info(ChildModules,
PoA, ImportAndOrUse, LeastContext),
map.det_update(ParentModule, MissingAncestorInfo,
!MissingAncestorMap)
else
ChildModules = set.make_singleton_set(ImportedModule),
HeadIoUC = import_or_use_context(HeadImportOrUse, HeadContext),
(
HeadImportOrUse = import_decl,
ImportAndOrUse0 = import_only
;
HeadImportOrUse = use_decl,
ImportAndOrUse0 = use_only
),
list.foldl2(update_iu_and_least_context, TailIoUCs,
ImportAndOrUse0, ImportAndOrUse,
HeadContext, LeastContext),
MissingAncestorInfo = missing_ancestor_info(ChildModules,
ParentOrAncestor, ImportAndOrUse, LeastContext),
map.det_insert(ParentModule, MissingAncestorInfo,
!MissingAncestorMap),
find_any_missing_ancestor_imports(CurrentModule, poa_ancestor,
ImportUseMap, ParentModule, IoUCs, !MissingAncestorMap)
)
)
;
ImportedModule = unqualified(_)
% For modules without parent modules, accessibility is moot.
).
:- pred update_iu_and_least_context(import_or_use_context::in,
import_and_or_use::in, import_and_or_use::out,
term_context::in, term_context::out) is det.
update_iu_and_least_context(IoUC, !ImportAndOrUse, !LeastContext) :-
IoUC = import_or_use_context(ImportOrUse, Context),
(
ImportOrUse = import_decl,
(
!.ImportAndOrUse = import_only
;
( !.ImportAndOrUse = use_only
; !.ImportAndOrUse = import_and_use
),
!:ImportAndOrUse = import_and_use
)
;
ImportOrUse = use_decl,
(
!.ImportAndOrUse = use_only
;
( !.ImportAndOrUse = import_only
; !.ImportAndOrUse = import_and_use
),
!:ImportAndOrUse = import_and_use
)
),
( if
compare((<), Context, !.LeastContext),
not is_dummy_context(Context)
then
!:LeastContext = Context
else
true
).
%---------------------------------------------------------------------------%
%
% Error reporting.
%
:- type missing_where
---> missing_in_src_int(module_import_or_use_map)
; missing_in_src_imp.
:- pred report_missing_ancestor(module_name::in,
missing_where::in, module_name::in, missing_ancestor_info::in,
list(module_name)::in, list(module_name)::out,
list(error_spec)::in, list(error_spec)::out) is det.
report_missing_ancestor(ModuleName, MissingWhere,
MissingModuleName, MissingAncestorInfo, !ModulesToAdd, !Specs) :-
!:ModulesToAdd = [MissingModuleName | !.ModulesToAdd],
MissingAncestorInfo = missing_ancestor_info(DescendantModuleNamesSet,
MaxDepth, ImportAndOrUse, LeastContext),
set.to_sorted_list(DescendantModuleNamesSet, DescendantModuleNames),
( MaxDepth = poa_parent, ChildOrDescendant = "child"
; MaxDepth = poa_ancestor, ChildOrDescendant = "descendant"
),
(
ImportAndOrUse = import_only,
DeclPieces = [decl("import_module")]
;
ImportAndOrUse = use_only,
DeclPieces = [decl("use_module")]
;
ImportAndOrUse = import_and_use,
DeclPieces = [decl("import_module"), words("and"), decl("use_module")]
),
(
MissingWhere = missing_in_src_int(_),
InTheInterface = [words("in the interface")]
;
MissingWhere = missing_in_src_imp,
InTheInterface = []
),
DescendantPieces = list.map(wrap_module_name, DescendantModuleNames),
ModuleS = choose_number(DescendantModuleNames, "module", "modules"),
DeclarationS = choose_number(DescendantModuleNames,
"declaration", "declarations"),
MainPieces = [words("In module"), qual_sym_name(ModuleName),
suffix(":"), words("error:"), nl,
words("the absence of an")] ++
color_as_correct([decl("import_module")]) ++
[words("or")] ++
color_as_correct([decl("use_module"), words("declaration")]) ++
[words("for")] ++
color_as_subject([qual_sym_name(MissingModuleName)] ++
InTheInterface) ++
color_as_incorrect([words("prevents access")]) ++
[words("to the")] ++ DeclPieces ++ [words(DeclarationS)] ++
InTheInterface ++
[words("for its"), words(ChildOrDescendant), words(ModuleS)] ++
piece_list_to_pieces("and", DescendantPieces) ++
[suffix("."), nl],
MainMsg = msg(LeastContext, MainPieces),
( if
MissingWhere = missing_in_src_int(SrcImpImportUseMap),
map.search(SrcImpImportUseMap, MissingModuleName, IoUCs)
then
% XXX _TailIoUCs
IoUCs = one_or_more(HeadIoUC, _TailIoUCs),
HeadIoUC = import_or_use_context(ImportOrUse, ImpContext),
( ImportOrUse = import_decl, ImportOrUseDecl = "import_module"
; ImportOrUse = use_decl, ImportOrUseDecl = "use_module"
),
ImpPieces = [words("Adding such a declaration would obsolete"),
words("this"), decl(ImportOrUseDecl), words("declaration"),
words("in the implementation section."), nl],
ImpMsg = msg(ImpContext, ImpPieces),
Msgs = [MainMsg, ImpMsg]
else
Msgs = [MainMsg]
),
Spec = error_spec($pred, severity_error, phase_pt2h, Msgs),
!:Specs = [Spec | !.Specs].
%---------------------%
:- pred report_any_missing_includes_for_imports(set(module_name)::in,
seen_includes::in, module_inclusion_map::in,
module_name::in, one_or_more(import_or_use_context)::in,
list(error_spec)::in, list(error_spec)::out) is det.
report_any_missing_includes_for_imports(ReadModules, SeenIncludes, InclMap,
ModuleName, IoUCs, !Specs) :-
IoUCs = one_or_more(HeadIoUC, TailIoUCs),
Contexts = list.map(project_out_import_or_use, [HeadIoUC | TailIoUCs]),
report_any_missing_includes(ReadModules, SeenIncludes, InclMap,
ModuleName, Contexts, !Specs).
% report_any_missing_includes(ReadModules, InclMap, Module, Contexts,
% !Specs):
%
% If Module is a submodule of ParentModule but we haven't seen
% an include_module declaration for Module in ParentModule even though
% we should have seen it if it exists (because we have read an interface
% file for ParentModule, which should contain all its include_module
% declarations), then add an error message reporting this fact to !Specs.
%
:- pred report_any_missing_includes(set(module_name)::in,
seen_includes::in, module_inclusion_map::in,
module_name::in, list(term_context)::in,
list(error_spec)::in, list(error_spec)::out) is det.
report_any_missing_includes(ReadModules, SeenIncludes, InclMap,
Module, Contexts, !Specs) :-
(
Module = qualified(ParentModule, SubModule),
( if map.search(InclMap, Module, IncludeContexts) then
% Module *has* its include in ParentModule, ...
IncludeContexts =
one_or_more(HeadIncludeContext, TailIncludeContexts),
IncludeContextsList = [HeadIncludeContext | TailIncludeContexts],
( if any_true(is_non_abstract_include, IncludeContextsList) then
% ... and it is visible here.
true
else
% ... and it is NOT visible here.
list.foldl(report_abstract_include(ParentModule, SubModule),
Contexts, !Specs)
)
else
% We have not seen Module's include in ParentModule.
( if set.contains(ReadModules, ParentModule) then
% We have read item blocks from ParentModule, and they
% *should* have included its include_module declarations.
list.foldl(
report_missing_include(SeenIncludes,
ParentModule, SubModule),
Contexts, !Specs)
else
% We have not read any item blocks from ParentModule.
% For all we know, ParentModule *may* contain an include
% for Module; we just don't know. Reporting an error
% would be misleading.
%
% If we had imported ParentModule, we would have read
% item blocks from one of its interface files. We will
% report the missing import. If the include is truly missing
% in ParentModule, we will discover and report that fact
% when the missing import of ParentModule in the *current*
% module is fixed by the programmer.
true
)
),
report_any_missing_includes(ReadModules, SeenIncludes, InclMap,
ParentModule, Contexts, !Specs)
;
Module = unqualified(_)
% For modules without parent modules, accessibility is moot.
).
:- pred report_abstract_include(module_name::in, string::in, term_context::in,
list(error_spec)::in, list(error_spec)::out) is det.
report_abstract_include(ParentModule, SubModule, Context, !Specs) :-
Pieces = [words("Error:"),
words("module"), qual_sym_name(ParentModule),
words("has a submodule named")] ++
color_as_subject([quote(SubModule), suffix(",")]) ++
[words("but it is")] ++
color_as_incorrect([words("visible only to its other submodules.")]) ++
[nl],
Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces),
!:Specs = [Spec | !.Specs].
:- pred report_missing_include(seen_includes::in, module_name::in, string::in,
term_context::in, list(error_spec)::in, list(error_spec)::out) is det.
report_missing_include(SeenIncludes, ParentModule, SubModule, Context,
!Specs) :-
(
SeenIncludes = seen_all_includes,
SubmodulePieces = [words("a submodule")]
;
SeenIncludes = seen_only_int_includes,
SubmodulePieces = [words("a visible submodule")]
),
Pieces = [words("Error:"), words("module")] ++
color_as_subject([qual_sym_name(ParentModule)]) ++
color_as_incorrect([words("does not have")]) ++
SubmodulePieces ++ [words("named")] ++
color_as_subject([quote(SubModule), suffix(".")]) ++
[nl],
Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces),
!:Specs = [Spec | !.Specs].
%---------------------------------------------------------------------------%
%
% Utility operations.
%
:- pred append_one_or_more(one_or_more(T)::in, one_or_more(T)::in,
one_or_more(T)::out) is det.
append_one_or_more(A, B, AB) :-
A = one_or_more(HeadA, TailA),
B = one_or_more(HeadB, TailB),
AB = one_or_more(HeadA, TailA ++ [HeadB | TailB]).
:- pred is_non_abstract_include(include_context::in) is semidet.
is_non_abstract_include(IncludeContext) :-
IncludeContext = include_context(MaybeAbstractInclude, _Context),
MaybeAbstractInclude = non_abstract_section.
:- func project_out_import_or_use(import_or_use_context) = term_context.
project_out_import_or_use(import_or_use_context(_, Context)) = Context.
:- func wrap_module_name(sym_name) = format_piece.
wrap_module_name(Module) = qual_sym_name(Module).
%---------------------------------------------------------------------------%
:- end_module parse_tree.check_import_accessibility.
%---------------------------------------------------------------------------%