[m-rev.] for review: Accept code depending on old visibility rule.

Peter Wang novalazy at gmail.com
Tue Aug 18 15:56:03 AEST 2026


In commit a5f5f7dbc7, two changes were made to module visibility rules.
The first change was that an entity would no longer be visible in the
interface section of a submodule, as a result of that entity being
imported into the implementation section of an ancestor module.

To smoothen the transition, this commit allows the compiler to accept
code that depends on the older visibility rule (where entities imported
in an ancestor's implementation section ARE made visible in the
interface sections of its descendants), but generate a warning if the
older rule is used. The warning can be disabled with the option
`--no-warn-old-submodule-visibility-rule'.

Of course, the rule was changed in response to Mantis bug #584.
If the user ignores the warning and doesn't update the code,
they may hit the problem in Mantis bug #584, but that is fairly rare.

compiler/comp_unit_interface.m:
    Make the new warning message be printed, but not stop interface
    files from being written out.

compiler/module_qual.mq_info.m:
    Set up mq_info structure to hold warnings.

compiler/module_qual.collect_mq_info.m:
compiler/module_qual.id_set.m:
compiler/module_qual.qual_errors.m:
compiler/module_qual.qualify_items.m:
    Implement the old visibility rule during module qualification,
    but emit a warning if the old rule is used.

compiler/make_hlds_passes.m:
    Conform to changes.

compiler/option_categories.m:
compiler/options.m:
compiler/print_help.m:
    Add a oc_warn_lang_change category containing a
    warn_old_submodule_visibility_rule option.

tests/invalid_submodules/Mmakefile:
tests/invalid_submodules/bug584.err_exp:
tests/invalid_submodules/bug584.err_exp2:
tests/invalid_submodules/bug584.m:
    Update test case. The --make-int step now emits a warning
    but generates the interface file.

tests/warnings/help_text.err_exp:
    Expect the new help text.

NEWS.md:
    Announce change.
---
 NEWS.md                                  |  5 +-
 compiler/error_util.m                    | 17 ++++-
 compiler/make_hlds_passes.m              |  6 +-
 compiler/module_qual.collect_mq_info.m   | 49 +++++++++---
 compiler/module_qual.id_set.m            | 97 ++++++++++++++++++------
 compiler/module_qual.mq_info.m           | 25 ++++--
 compiler/module_qual.qual_errors.m       | 30 ++++++++
 compiler/module_qual.qualify_items.m     |  7 +-
 compiler/option_categories.m             |  5 +-
 compiler/options.m                       | 10 +++
 compiler/print_help.m                    |  5 ++
 tests/invalid_submodules/Mmakefile       | 14 ++--
 tests/invalid_submodules/bug584.err_exp  | 15 ++--
 tests/invalid_submodules/bug584.err_exp2 | 13 ----
 tests/invalid_submodules/bug584.m        |  5 --
 tests/warnings/help_text.err_exp         |  6 ++
 16 files changed, 231 insertions(+), 78 deletions(-)
 delete mode 100644 tests/invalid_submodules/bug584.err_exp2

diff --git a/NEWS.md b/NEWS.md
index bdc00270b7..731c2646c0 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -10,7 +10,10 @@ Changes that may break compatibility
     in the implementation section of a module will make the declarations
     in the interface section of `M` visible in the implementation sections
     of its submodules only. Previously, the declarations would also be also
-    visible in the submodules' interface sections.
+    visible in the submodules' interface sections. Code depending on the
+    old behaviour will be accepted for now with a warning message.
+    The warning can be disabled with the
+    `--no-warn-old-submodule-visibility-rule` option.
 
   - If a module contains `:- import_module` or `:- use_module` declarations
     that import module `M`, visibility of M's exports within that module
diff --git a/compiler/error_util.m b/compiler/error_util.m
index 89a93ecfe8..ba11200c67 100644
--- a/compiler/error_util.m
+++ b/compiler/error_util.m
@@ -511,12 +511,27 @@ filter_interface_generation_specs(Globals, Specs, SpecsToPrint) :-
         halt_at_invalid_interface, HaltInvalidInterface),
     (
         HaltInvalidInterface = yes,
-        list.filter(does_spec_print_anything(Globals), Specs, SpecsToPrint)
+        list.filter(keep_spec_for_interface_generation(Globals),
+            Specs, SpecsToPrint)
     ;
         HaltInvalidInterface = no,
         SpecsToPrint = []
     ).
 
+:- pred keep_spec_for_interface_generation(globals::in, diag_spec::in)
+    is semidet.
+
+keep_spec_for_interface_generation(Globals, Spec) :-
+    does_spec_print_anything(Globals, Spec),
+    not (
+        % We will warn about code that depends on the old submodule visibility
+        % rule when compiling the module, but we do not want the warning to
+        % stop the interface file from being generated when
+        % halt_at_invalid_interface is enabled.
+        Spec = gen_spec(_Id, Severity, _Phase, _Msgs),
+        Severity = severity_warning(warn_old_submodule_visibility_rule)
+    ).
+
 %---------------------------------------------------------------------------%
 
 :- type diag_spec_accumulator == maybe(pair(set(diag_spec))).
diff --git a/compiler/make_hlds_passes.m b/compiler/make_hlds_passes.m
index aaffc1a1ca..27c236119d 100644
--- a/compiler/make_hlds_passes.m
+++ b/compiler/make_hlds_passes.m
@@ -644,10 +644,12 @@ parse_tree_to_hlds(ProgressStream, AugCompUnit, Globals, DumpBaseFileName,
 
     qual_info_get_mq_info(!.QualInfo, MQInfo),
     get_err_specs_in_mq_info(MQInfo,
-        MQInvalidTypeSpecs, MQInvalidInstModeSpecs, MQNonBlockingUndefSpecs),
+        MQInvalidTypeSpecs, MQInvalidInstModeSpecs, MQNonBlockingUndefSpecs,
+        WarnSpecs),
     !:InvalidTypeSpecs = MQInvalidTypeSpecs ++ !.InvalidTypeSpecs,
     !:InvalidInstModeSpecs = MQInvalidInstModeSpecs ++ !.InvalidInstModeSpecs,
-    !:ErrSpecs = MQNonBlockingUndefSpecs ++ !.ErrSpecs.
+    !:ErrSpecs = MQNonBlockingUndefSpecs ++ !.ErrSpecs,
+    !:WarnSpecs = WarnSpecs ++ !.WarnSpecs.
 
 %---------------------------------------------------------------------------%
 
diff --git a/compiler/module_qual.collect_mq_info.m b/compiler/module_qual.collect_mq_info.m
index 1151643f79..366e8f2a37 100644
--- a/compiler/module_qual.collect_mq_info.m
+++ b/compiler/module_qual.collect_mq_info.m
@@ -298,9 +298,9 @@ collect_mq_info_in_parse_tree_int1(ReadWhy1, ParseTreeInt1, !Info) :-
         IntPermInInt = may_use_in_int(may_be_unqualified),
         IntPermInImp = may_use_in_imp(may_be_unqualified)
     ;
-        ( ReadWhy1 = rwi1_ancestor_imp_import
-        ; ReadWhy1 = rwi1_imp_import
-        ),
+        % New submodule visibility rule:
+        % ReadWhy1 = rwi1_ancestor_imp_import
+        ReadWhy1 = rwi1_imp_import,
         IntPermInInt = may_not_use_in_int,
         IntPermInImp = may_use_in_imp(may_be_unqualified)
     ;
@@ -310,11 +310,24 @@ collect_mq_info_in_parse_tree_int1(ReadWhy1, ParseTreeInt1, !Info) :-
         IntPermInInt = may_use_in_int(must_be_qualified),
         IntPermInImp = may_use_in_imp(must_be_qualified)
     ;
-        ( ReadWhy1 = rwi1_ancestor_imp_use
-        ; ReadWhy1 = rwi1_imp_use
-        ),
+        % New submodule visibility rule:
+        % ReadWhy1 = rwi1_ancestor_imp_use
+        ReadWhy1 = rwi1_imp_use,
         IntPermInInt = may_not_use_in_int,
         IntPermInImp = may_use_in_imp(must_be_qualified)
+    ;
+        % Old submodule visibility rule: during the transition,
+        % allow entities imported in an ancestor implementation section
+        % to be visible in the interface section, but generate a warning
+        % if that is how an entity is used.
+        ReadWhy1 = rwi1_ancestor_imp_import,
+        IntPermInInt = may_use_in_int_warn(may_be_unqualified),
+        IntPermInImp = may_use_in_imp(may_be_unqualified)
+    ;
+        % Old submodule visibility rule: as above.
+        ReadWhy1 = rwi1_ancestor_imp_use,
+        IntPermInInt = may_use_in_int_warn(must_be_qualified),
+        IntPermInImp = may_use_in_imp(must_be_qualified)
     ;
         ReadWhy1 = rwi1_int_use_imp_import,
         IntPermInInt = may_use_in_int(must_be_qualified),
@@ -630,18 +643,32 @@ collect_mq_info_in_parse_tree_int3(Role, ParseTreeInt3, !Info) :-
             PermInInt = may_use_in_int(must_be_qualified),
             PermInImp = may_use_in_imp(must_be_qualified)
         ;
-            ( ReadWhy3 = rwi3_direct_ancestor_imp_import
-            ; ReadWhy3 = rwi3_direct_imp_import
-            ),
+            % New submodule visibility rule:
+            % ReadWhy3 = rwi3_direct_ancestor_imp_import
+            ReadWhy3 = rwi3_direct_imp_import,
             PermInInt = may_not_use_in_int,
             PermInImp = may_use_in_imp(may_be_unqualified)
         ;
-            ( ReadWhy3 = rwi3_direct_ancestor_imp_use
-            ; ReadWhy3 = rwi3_direct_imp_use
+            % New submodule visibility rule:
+            % ReadWhy3 = rwi3_direct_ancestor_imp_use
+            ( ReadWhy3 = rwi3_direct_imp_use
             ; ReadWhy3 = rwi3_indirect_imp_use
             ),
             PermInInt = may_not_use_in_int,
             PermInImp = may_use_in_imp(must_be_qualified)
+        ;
+            % Old submodule visibility rule: during the transition,
+            % allow entities imported in an ancestor implementation section
+            % to be visible in the interface section, but generate a warning
+            % if that is how an entity is used.
+            ReadWhy3 = rwi3_direct_ancestor_imp_import,
+            PermInInt = may_use_in_int_warn(may_be_unqualified),
+            PermInImp = may_use_in_imp(may_be_unqualified)
+        ;
+            % Old submodule visibility rule: as above.
+            ReadWhy3 = rwi3_direct_ancestor_imp_use,
+            PermInInt = may_use_in_int_warn(must_be_qualified),
+            PermInImp = may_use_in_imp(must_be_qualified)
         ;
             ReadWhy3 = rwi3_direct_int_use_imp_import,
             PermInInt = may_use_in_int(must_be_qualified),
diff --git a/compiler/module_qual.id_set.m b/compiler/module_qual.id_set.m
index 80202b832f..7126dc7ec6 100644
--- a/compiler/module_qual.id_set.m
+++ b/compiler/module_qual.id_set.m
@@ -67,7 +67,8 @@
 
 :- type perm_in_int
     --->    may_not_use_in_int
-    ;       may_use_in_int(need_qualifier).
+    ;       may_use_in_int(need_qualifier)
+    ;       may_use_in_int_warn(need_qualifier).
 
 :- type perm_in_imp
     --->    may_use_in_imp(need_qualifier).
@@ -245,15 +246,31 @@ insert_into_permissions_map(NewPermissions, ModuleName, !PermissionsMap) :-
             OldPermInt = may_not_use_in_int,
             PermInt = NewPermInt
         ;
-            OldPermInt = may_use_in_int(OldIntNeedQual),
+            (
+                OldPermInt = may_use_in_int(OldIntNeedQual),
+                OldWarn = no
+            ;
+                OldPermInt = may_use_in_int_warn(OldIntNeedQual),
+                OldWarn = yes
+            ),
             (
                 NewPermInt = may_not_use_in_int,
                 PermInt = OldPermInt
             ;
-                NewPermInt = may_use_in_int(NewIntNeedQual),
+                (
+                    NewPermInt = may_use_in_int(NewIntNeedQual),
+                    NewWarn = no
+                ;
+                    NewPermInt = may_use_in_int_warn(NewIntNeedQual),
+                    NewWarn = yes
+                ),
                 need_qual_only_if_both(OldIntNeedQual, NewIntNeedQual,
                     IntNeedQual),
-                PermInt = may_use_in_int(IntNeedQual)
+                ( if NewWarn = yes, OldWarn = yes then
+                    PermInt = may_use_in_int_warn(IntNeedQual)
+                else
+                    PermInt = may_use_in_int(IntNeedQual)
+                )
             )
         ),
         OldPermImp = may_use_in_imp(OldImpNeedQual),
@@ -304,7 +321,7 @@ find_unique_match(InInt, ErrorContext, IdSet, IdType, Id0, SymName, !Info) :-
     (
         Matches = [],
         % No matches for this id.
-        MaybeUniqModuleName = no,
+        MaybeUniqMatchType = no,
         mq_info_get_should_report_errors(!.Info, ReportErrors),
         (
             ReportErrors = should_report_errors,
@@ -326,29 +343,38 @@ find_unique_match(InInt, ErrorContext, IdSet, IdType, Id0, SymName, !Info) :-
             ReportErrors = should_not_report_errors
         )
     ;
-        Matches = [ModuleName],
+        Matches = [ModuleMatchType],
         % A unique match for this ID.
-        MaybeUniqModuleName = yes(ModuleName)
+        MaybeUniqMatchType = yes(ModuleMatchType)
     ;
         Matches = [_, _ | _],
-        MaybeUniqModuleName = no,
+        MaybeUniqMatchType = no,
         mq_info_get_should_report_errors(!.Info, ReportErrors),
         (
             ReportErrors = should_report_errors,
+            UsableModuleNames = list.map(matched_module_name, Matches),
             NonUsableModuleNames = IntMismatches ++ QualMismatches,
             report_ambiguous_match(ErrorContext, Id0, IdType,
-                Matches, NonUsableModuleNames, Spec),
+                UsableModuleNames, NonUsableModuleNames, Spec),
             mq_info_record_undef_mq_id(IdType, Id0, Spec, !Info)
         ;
             ReportErrors = should_not_report_errors
         )
     ),
     (
-        MaybeUniqModuleName = no,
+        MaybeUniqMatchType = no,
         % Returning any SymName is fine, since it won't be used.
         Id0 = mq_id(SymName, _)
     ;
-        MaybeUniqModuleName = yes(UniqModuleName),
+        MaybeUniqMatchType = yes(UniqMatchType),
+        (
+            UniqMatchType = match(UniqModuleName)
+        ;
+            UniqMatchType = match_with_warning(UniqModuleName),
+            report_old_submodule_visibility_match(ErrorContext, Id0, IdType,
+                UniqModuleName, WarnSpec),
+            mq_info_record_warning(WarnSpec, !Info)
+        ),
         SymName = qualified(UniqModuleName, BaseName),
         mq_info_set_module_used(InInt, UniqModuleName, !Info),
         UsedItemType = convert_used_item_type(IdType),
@@ -399,6 +425,14 @@ mq_info_record_undef_mq_id(IdType, Id, Spec, !Info) :-
         )
     ).
 
+:- pred mq_info_record_warning(warn_spec::in, mq_info::in, mq_info::out)
+    is det.
+
+mq_info_record_warning(Spec, !Info) :-
+    mq_info_get_warn_specs(!.Info, Specs0),
+    Specs = [Spec | Specs0],
+    mq_info_set_warn_specs(Specs, !Info).
+
 :- func convert_used_item_type(qual_id_kind) = used_item_type.
 
 convert_used_item_type(qual_id_type) = used_type_name.
@@ -408,8 +442,17 @@ convert_used_item_type(qual_id_class) = used_typeclass.
 
 %---------------------------------------------------------------------------%
 
+:- type module_match_type
+    --->    match(module_name)
+    ;       match_with_warning(module_name).
+
+:- func matched_module_name(module_match_type) = module_name.
+
+matched_module_name(match(ModuleName)) = ModuleName.
+matched_module_name(match_with_warning(ModuleName)) = ModuleName.
+
 :- pred id_set_search_sym_arity(mq_in_interface::in, id_set::in,
-    sym_name::in, string::in, int::in, list(module_name)::out,
+    sym_name::in, string::in, int::in, list(module_match_type)::out,
     list(module_name)::out, list(module_name)::out) is det.
 
 id_set_search_sym_arity(InInt, IdSet, SymName, UnqualName, Arity,
@@ -427,7 +470,7 @@ id_set_search_sym_arity(InInt, IdSet, SymName, UnqualName, Arity,
     ).
 
 :- pred find_matches_in_permissions_map(mq_in_interface::in, sym_name::in,
-    permissions_map::in, list(module_name)::out,
+    permissions_map::in, list(module_match_type)::out,
     list(module_name)::out, list(module_name)::out) is det.
 
 find_matches_in_permissions_map(InInt, SymName, PermissionsMap,
@@ -437,7 +480,7 @@ find_matches_in_permissions_map(InInt, SymName, PermissionsMap,
 
 :- 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_match_type)::in, list(module_match_type)::out,
     list(module_name)::in, list(module_name)::out,
     list(module_name)::in, list(module_name)::out) is det.
 
@@ -477,7 +520,7 @@ add_matching_and_nearmiss_modules(InInt, SymName, ModuleName, Permissions,
 
 :- 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_match_type)::in, list(module_match_type)::out,
     list(module_name)::in, list(module_name)::out,
     list(module_name)::in, list(module_name)::out) is det.
 
@@ -490,30 +533,40 @@ add_matching_and_nearmiss_modules_int(InInt, FullyModuleQualified,
             PermInInt = may_not_use_in_int,
             !:IntMismatches = [ModuleName | !.IntMismatches]
         ;
-            PermInInt = may_use_in_int(NeedQual),
+            ( PermInInt = may_use_in_int(NeedQual), MaybeWarn = no
+            ; PermInInt = may_use_in_int_warn(NeedQual), MaybeWarn = yes
+            ),
             add_matching_and_nearmiss_modules_qual(FullyModuleQualified,
-                NeedQual, ModuleName, !Matches, !QualMismatches)
+                NeedQual, ModuleName, MaybeWarn, !Matches, !QualMismatches)
         )
     ;
         InInt = mq_not_used_in_interface,
         PermInImp = may_use_in_imp(NeedQual),
+        MaybeWarn = no,
         add_matching_and_nearmiss_modules_qual(FullyModuleQualified,
-            NeedQual, ModuleName, !Matches, !QualMismatches)
+            NeedQual, ModuleName, MaybeWarn, !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,
+    module_name::in, bool::in,
+    list(module_match_type)::in, list(module_match_type)::out,
     list(module_name)::in, list(module_name)::out) is det.
 
 add_matching_and_nearmiss_modules_qual(FullyModuleQualified, NeedQual,
-        ModuleName, !Matches, !QualMismatches) :-
+        ModuleName, MaybeWarn, !Matches, !QualMismatches) :-
     ( if
         ( FullyModuleQualified = yes
         ; NeedQual = may_be_unqualified
         )
     then
-        !:Matches = [ModuleName | !.Matches]
+        (
+            MaybeWarn = no,
+            MatchType = match(ModuleName)
+        ;
+            MaybeWarn = yes,
+            MatchType = match_with_warning(ModuleName)
+        ),
+        !:Matches = [MatchType | !.Matches]
     else
         !:QualMismatches = [ModuleName | !.QualMismatches]
     ).
diff --git a/compiler/module_qual.mq_info.m b/compiler/module_qual.mq_info.m
index dc632a1ab2..66689b4c91 100644
--- a/compiler/module_qual.mq_info.m
+++ b/compiler/module_qual.mq_info.m
@@ -81,6 +81,8 @@
     is_undef_blocking::out) is det.
 :- pred mq_info_get_nonblocking_undef_specs(mq_info::in,
     list(err_spec)::out) is det.
+:- pred mq_info_get_warn_specs(mq_info::in,
+    list(warn_spec)::out) is det.
 :- pred mq_info_get_should_report_errors(mq_info::in,
     maybe_should_report_errors::out) is det.
 
@@ -117,6 +119,8 @@
     mq_info::in, mq_info::out) is det.
 :- pred mq_info_set_nonblocking_undef_specs(list(err_spec)::in,
     mq_info::in, mq_info::out) is det.
+:- pred mq_info_set_warn_specs(list(warn_spec)::in,
+    mq_info::in, mq_info::out) is det.
 
 %---------------------------------------------------------------------------%
 
@@ -171,10 +175,12 @@
 %---------------------------------------------------------------------------%
 
     % get_err_specs_in_mq_info(Info,
-    %   InvalidTypeSpecs, InvalidInstModeSpecs, NonBlockingUndefSpecs)
+    %   InvalidTypeSpecs, InvalidInstModeSpecs, NonBlockingUndefSpecs,
+    %   WarnSpecs)
     %
 :- pred get_err_specs_in_mq_info(mq_info::in,
-    list(err_spec)::out, list(err_spec)::out, list(err_spec)::out) is det.
+    list(err_spec)::out, list(err_spec)::out, list(err_spec)::out,
+    list(warn_spec)::out) is det.
 
 %---------------------------------------------------------------------------%
 %---------------------------------------------------------------------------%
@@ -249,6 +255,9 @@
                 mqsi_is_undef_blocking          :: is_undef_blocking,
                 mqsi_nonblocking_undef_specs    :: list(err_spec),
 
+                % Warnings generated during module qualification.
+                mqsi_warn_specs                 :: list(warn_spec),
+
                 % Do we want to report errors.
                 mqsi_should_report_errors       :: maybe_should_report_errors,
 
@@ -277,7 +286,7 @@ init_mq_info(Globals, ModuleName, ReportErrors, Info) :-
         InstanceModules, ExportedInstancesFlag,
         one_or_more_map.init, one_or_more_map.init,
         one_or_more_map.init, one_or_more_map.init,
-        undef_is_blocking, [], ReportErrors, 0),
+        undef_is_blocking, [], [], ReportErrors, 0),
 
     id_set_init(ModuleIdSet),
     id_set_init(TypeIdSet),
@@ -335,6 +344,8 @@ mq_info_get_is_undef_blocking(Info, X) :-
     X = Info ^ mqi_sub_info ^ mqsi_is_undef_blocking.
 mq_info_get_nonblocking_undef_specs(Info, X) :-
     X = Info ^ mqi_sub_info ^ mqsi_nonblocking_undef_specs.
+mq_info_get_warn_specs(Info, X) :-
+    X = Info ^ mqi_sub_info ^ mqsi_warn_specs.
 mq_info_get_should_report_errors(Info, X) :-
     X = Info ^ mqi_sub_info ^ mqsi_should_report_errors.
 
@@ -371,6 +382,8 @@ mq_info_set_is_undef_blocking(X, !Info) :-
     !Info ^ mqi_sub_info ^ mqsi_is_undef_blocking := X.
 mq_info_set_nonblocking_undef_specs(X, !Info) :-
     !Info ^ mqi_sub_info ^ mqsi_nonblocking_undef_specs := X.
+mq_info_set_warn_specs(X, !Info) :-
+    !Info ^ mqi_sub_info ^ mqsi_warn_specs := X.
 
 %---------------------------------------------------------------------------%
 
@@ -458,7 +471,8 @@ mq_info_set_module_used(InInt, ModuleName, !Info) :-
 %---------------------------------------------------------------------------%
 
 get_err_specs_in_mq_info(Info,
-        InvalidTypeSpecs, InvalidInstModeSpecs, NonBlockingUndefSpecs) :-
+        InvalidTypeSpecs, InvalidInstModeSpecs, NonBlockingUndefSpecs,
+        WarnSpecs) :-
     mq_info_get_undef_types(Info, UndefTypesMap),
     mq_info_get_undef_insts(Info, UndefInstsMap),
     mq_info_get_undef_modes(Info, UndefModesMap),
@@ -469,7 +483,8 @@ get_err_specs_in_mq_info(Info,
     one_or_more_map.values(UndefTypeClassesMap, UndefTypeClassSpecs),
     InvalidTypeSpecs = UndefTypeSpecs ++ UndefTypeClassSpecs,
     InvalidInstModeSpecs = UndefInstSpecs ++ UndefModeSpecs,
-    mq_info_get_nonblocking_undef_specs(Info, NonBlockingUndefSpecs).
+    mq_info_get_nonblocking_undef_specs(Info, NonBlockingUndefSpecs),
+    mq_info_get_warn_specs(Info, WarnSpecs).
 
 %---------------------------------------------------------------------------%
 :- end_module parse_tree.module_qual.mq_info.
diff --git a/compiler/module_qual.qual_errors.m b/compiler/module_qual.qual_errors.m
index af9b6cee4e..52b8fca8e0 100644
--- a/compiler/module_qual.qual_errors.m
+++ b/compiler/module_qual.qual_errors.m
@@ -195,6 +195,17 @@
     qual_id_kind::in,
     list(module_name)::in, list(module_name)::in, err_spec::out) is det.
 
+    % report_old_submodule_visibility_match(ErrorContext, Id, IdType,
+    %   ModuleName, Spec):
+    %
+    % Report a warning where an entity was matched but used the deprecated
+    % submodule visibility rule where entities imported in the implementation
+    % section of an ancestor are made visible in the interface section of its
+    % descendant modules.
+    %
+:- pred report_old_submodule_visibility_match(mq_error_context::in, mq_id::in,
+    qual_id_kind::in, module_name::in, warn_spec::out) is det.
+
     % Output an error message about an ill-formed user_inst.
     %
 :- pred report_invalid_user_inst(sym_name::in, list(mer_inst)::in,
@@ -447,6 +458,25 @@ report_ambiguous_match(ErrorContext, Id, IdType,
         verbose_only(verbose_always, VerbosePieces)]),
     Spec = gen_spec($pred, severity_error, phase_pt2h, [Msg]).
 
+report_old_submodule_visibility_match(ErrorContext, Id, IdType, ModuleName,
+        Spec) :-
+    mq_error_context_to_pieces(ErrorContext, Context, _ShouldUnqualId,
+        ErrorContextPieces),
+    qual_id_kind_to_string(IdType, IdTypeStr),
+    MainPieces = [words("In")] ++ ErrorContextPieces ++ [suffix(":"), nl,
+        words("the"), fixed(IdTypeStr)] ++
+        color_as_subject([wrap_qual_id(Id)]) ++
+        [words("is only visible here due to an import of")] ++
+        color_as_subject([wrap_module_name(ModuleName)]) ++
+        [words("in an ancestor module's implementation section."),
+        words("This behaviour is")] ++
+        color_as_incorrect([words("deprecated.")]) ++
+        [nl],
+    Msg = simple_msg(Context, [always(MainPieces)]),
+    Spec = gen_spec($pred,
+        severity_warning(warn_old_submodule_visibility_rule), phase_pt2h,
+        [Msg]).
+
 %---------------------------------------------------------------------------%
 
 report_invalid_user_inst(_SymName, _Insts, ErrorContext, Spec) :-
diff --git a/compiler/module_qual.qualify_items.m b/compiler/module_qual.qualify_items.m
index 9de22544eb..6fd8ada189 100644
--- a/compiler/module_qual.qualify_items.m
+++ b/compiler/module_qual.qualify_items.m
@@ -200,9 +200,11 @@ module_qualify_aug_make_int_unit(Globals, AugMakeIntUnit0, AugMakeIntUnit,
             AncestorInt0s, DirectInt3Specs, IndirectInt3Specs,
             ModuleVersionNumbers),
         get_err_specs_in_mq_info(!.Info,
-            InvalidTypeSpecs, InvalidInstModeSpecs, NonBlockingUndefSpecs),
+            InvalidTypeSpecs, InvalidInstModeSpecs, NonBlockingUndefSpecs,
+            WarnSpecs),
         !:ErrSpecs = InvalidTypeSpecs ++ InvalidInstModeSpecs ++
             NonBlockingUndefSpecs ++ !.ErrSpecs,
+        !:WarnSpecs = WarnSpecs ++ !.WarnSpecs,
 
         globals.lookup_bool_option(Globals, warn_unused_interface_imports,
             WarnUnusedInterfaceImports),
@@ -273,7 +275,8 @@ module_qualify_parse_tree_int3(Globals, OrigParseTreeInt3, ParseTreeInt3,
     qualify_parse_tree_int3(OrigParseTreeInt3, ParseTreeInt3,
         Info1, Info),
     get_err_specs_in_mq_info(Info,
-        InvalidTypeSpecs, InvalidInstModeSpecs, NonBlockingUndefSpecs),
+        InvalidTypeSpecs, InvalidInstModeSpecs, NonBlockingUndefSpecs,
+        _WarnSpecs),
     !:ErrSpecs = InvalidTypeSpecs ++ InvalidInstModeSpecs ++
         NonBlockingUndefSpecs ++ !.ErrSpecs.
 
diff --git a/compiler/option_categories.m b/compiler/option_categories.m
index aec1a21423..b55ea57de4 100644
--- a/compiler/option_categories.m
+++ b/compiler/option_categories.m
@@ -1,7 +1,7 @@
 %---------------------------------------------------------------------------%
 % vim: ft=mercury ts=4 sw=4 et
 %---------------------------------------------------------------------------%
-% Copyright (C) 2025 The Mercury team.
+% Copyright (C) 2025-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.
 %---------------------------------------------------------------------------%
@@ -72,6 +72,8 @@
     ;       oc_warn_style_ctg
     ;       oc_warn_style_ctg_c
             % Warnings about programming style.
+    ;       oc_warn_lang_change
+            % Warnings about language changes.
     ;       oc_warn_ctrl
             % Options that *control* warnings.
             % XXX Split into subparts, one for each of oc_warn_*
@@ -234,6 +236,7 @@ option_categories(oc_warn_style_ctg, 0).
 option_categories(oc_warn_style_ctg_c, 0).
 option_categories(oc_warn_style_goal, 0).
 option_categories(oc_warn_style_goal_c, 0).
+option_categories(oc_warn_lang_change, 0).
 option_categories(oc_warn_ctrl, 0).
 option_categories(oc_warn_halt, 0).
 option_categories(oc_inform, 0).
diff --git a/compiler/options.m b/compiler/options.m
index 9358fce3c4..0ed3cc52f4 100644
--- a/compiler/options.m
+++ b/compiler/options.m
@@ -547,6 +547,9 @@
     ;       warn_non_contiguous_foreign_procs
     ;       allow_non_contiguity_for
 
+    % Warnings about language changes.
+    ;       warn_old_submodule_visibility_rule
+
     % Options that control warnings.
     ;       inhibit_warnings
     ;       inhibit_style_warnings
@@ -3020,6 +3023,13 @@ optdb(oc_warn_style_ctg_c, allow_non_contiguity_for,   accumulating([]),
         w("that may be intermingled. Each name must uniquely specify"),
         w("a predicate or a function.")])).
 
+% Warnings about language changes.
+
+optdb(oc_warn_lang_change, warn_old_submodule_visibility_rule, bool(yes),
+    help("warn-old-submodule-visibility-rule", [
+        w("Do not generate a warning if an entity is only visible"),
+        w("using a deprecated submodule visibility rule.")])).
+
 %---------------------%
 
 optdb(oc_warn_ctrl, inhibit_warnings,                  bool_special,
diff --git a/compiler/print_help.m b/compiler/print_help.m
index 16a15d77e9..6f64db84e9 100644
--- a/compiler/print_help.m
+++ b/compiler/print_help.m
@@ -375,6 +375,10 @@ all_chapters = AllSections :-
         SubSubSectionWarnStyleGoal, SubSubSectionWarnStyleOrder,
         SubSubSectionWarnStyleContig]),
 
+    SubSubSectionWarnLangChange = help_atomic(help_option_group(
+        "Warnings about language changes",
+        "", [], [oc_warn_lang_change])),
+
     SubSectionWarnCtrl = help_atomic(help_option_group(
         "Options that control warnings",
         "", [], [oc_warn_ctrl])),
@@ -384,6 +388,7 @@ all_chapters = AllSections :-
     SectionWarn = help_composite("Warning options",
         "", [],
         [SubSectionWarnDodgy, SubSectionWarnPerf, SubSectionWarnStyle,
+        SubSubSectionWarnLangChange,
         SubSectionWarnCtrl, SubSectionWarnHalt]),
 
     % XXX Should these two chapters instead be two sections in one chapter?
diff --git a/tests/invalid_submodules/Mmakefile b/tests/invalid_submodules/Mmakefile
index c0937f50f4..24a4e9b1f0 100644
--- a/tests/invalid_submodules/Mmakefile
+++ b/tests/invalid_submodules/Mmakefile
@@ -122,18 +122,18 @@ $(MAKE_DEP_ONLY_PROGS:%=%.err): %.err: %.m
 	else true; \
 	fi
 
-# For this test case, the bug is caught when generating the interface for the
-# helper module.
+# For this test case, the bug should be caught when generating the interface
+# for the helper module. However, in the transition period, we will only
+# generate a warning while compiling the module.
 bug584.err: %.err: %.m
 	$(MC) --make-short-interface $(ALL_GRADEFLAGS) $(ALL_MCFLAGS) \
 		bug584_helper_1
 	$(MC) --make-private-interface $(ALL_GRADEFLAGS) $(ALL_MCFLAGS) \
 		bug584_helper_1
-	if $(MC) --make-interface $(ALL_GRADEFLAGS) $(ALL_MCFLAGS) \
-		bug584_helper_1 > $*.err 2>&1; \
-	then false; \
-	else true; \
-	fi
+	$(MC) --make-interface $(ALL_GRADEFLAGS) $(ALL_MCFLAGS) \
+		bug584_helper_1 > $*.err 2>&1
+	$(MC) -C $(ALL_GRADEFLAGS) $(ALL_MCFLAGS) \
+		bug584_helper_1 >> $*.err 2>&1
 
 $(SUB_VISIBILITY_PROGS:%=%.err): %.err: %.m
 	{ \
diff --git a/tests/invalid_submodules/bug584.err_exp b/tests/invalid_submodules/bug584.err_exp
index 95b33055c3..08d2c308db 100644
--- a/tests/invalid_submodules/bug584.err_exp
+++ b/tests/invalid_submodules/bug584.err_exp
@@ -1,12 +1,11 @@
 bug584_helper_1.m:018: In the second argument of function symbol `struct'
 bug584_helper_1.m:018:   (field name `f2') of the type `struct'/0:
-bug584_helper_1.m:018:   error: the type `foo'/0 is undefined.
-bug584_helper_1.m:018:   (The module `bug584_helper_1.sub2' has not been
-bug584_helper_1.m:018:   imported in the interface.)
+bug584_helper_1.m:018:   the type `foo'/0 is only visible here due to an import
+bug584_helper_1.m:018:   of `bug584_helper_1.sub2' in an ancestor module's
+bug584_helper_1.m:018:   implementation section. This behaviour is deprecated.
 bug584_helper_1.m:019: In the third argument of function symbol `struct' (field
 bug584_helper_1.m:019:   name `f3') of the type `struct'/0:
-bug584_helper_1.m:019:   error: the type `bug584_helper_1.sub2.foo'/0 is
-bug584_helper_1.m:019:   undefined.
-bug584_helper_1.m:019:   (The module `bug584_helper_1.sub2' has not been
-bug584_helper_1.m:019:   imported in the interface.)
-`bug584_helper_1.sub1.int' and `bug584_helper_1.sub1.int2' not written.
+bug584_helper_1.m:019:   the type `bug584_helper_1.sub2.foo'/0 is only visible
+bug584_helper_1.m:019:   here due to an import of `bug584_helper_1.sub2' in an
+bug584_helper_1.m:019:   ancestor module's implementation section. This
+bug584_helper_1.m:019:   behaviour is deprecated.
diff --git a/tests/invalid_submodules/bug584.err_exp2 b/tests/invalid_submodules/bug584.err_exp2
deleted file mode 100644
index 250e60a2ef..0000000000
--- a/tests/invalid_submodules/bug584.err_exp2
+++ /dev/null
@@ -1,13 +0,0 @@
-bug584_helper_1.m:018: In the second argument of function symbol `struct'
-bug584_helper_1.m:018:   (field name `f2') of the type `struct'/0:
-bug584_helper_1.m:018:   error: the type `foo'/0 is undefined.
-bug584_helper_1.m:018:   (The module `bug584_helper_1.sub2' has not been
-bug584_helper_1.m:018:   imported in the interface.)
-bug584_helper_1.m:019: In the third argument of function symbol `struct' (field
-bug584_helper_1.m:019:   name `f3') of the type `struct'/0:
-bug584_helper_1.m:019:   error: the type `bug584_helper_1.sub2.foo'/0 is
-bug584_helper_1.m:019:   undefined.
-bug584_helper_1.m:019:   (The module `bug584_helper_1.sub2' has not been
-bug584_helper_1.m:019:   imported in the interface.)
-`Mercury/ints/bug584_helper_1.sub1.int' and
-  `Mercury/int2s/bug584_helper_1.sub1.int2' not written.
diff --git a/tests/invalid_submodules/bug584.m b/tests/invalid_submodules/bug584.m
index ea0f2d28ae..0503baaa61 100644
--- a/tests/invalid_submodules/bug584.m
+++ b/tests/invalid_submodules/bug584.m
@@ -1,11 +1,6 @@
 %---------------------------------------------------------------------------%
 % vim: ts=4 sw=4 et ft=mercury
 %---------------------------------------------------------------------------%
-%
-% The .err_exp file is for --no-use-subdirs.
-% The .err_exp2 file is for --use-subdirs.
-%
-%---------------------------------------------------------------------------%
 
 :- module bug584.
 :- interface.
diff --git a/tests/warnings/help_text.err_exp b/tests/warnings/help_text.err_exp
index a4b6d74835..7d99c398a5 100644
--- a/tests/warnings/help_text.err_exp
+++ b/tests/warnings/help_text.err_exp
@@ -1331,6 +1331,12 @@ Warning options
         and/or function names that may be intermingled. Each name must uniquely
         specify a predicate or a function.
 
+  Warnings about language changes
+
+    --no-warn-old-submodule-visibility-rule
+        Do not generate a warning if an entity is only visible using a
+        deprecated submodule visibility rule.
+
   Options that control warnings
 
     -w
-- 
2.54.0



More information about the reviews mailing list