[m-rev.] for review: Accept code depending on shadowed ancestor imports.

Zoltan Somogyi zoltan.somogyi at runbox.com
Wed Aug 26 22:25:44 AEST 2026



On Wed, 26 Aug 2026 15:58:41 +1000, Peter Wang <novalazy at gmail.com> wrote:
> compiler/module_qual.collect_mq_info.m:
>     When collecting permissions for entities from .int or .int files,

The suffix 3 is missing from one of the ".int"s.

>     if the interface was also read due to a shadowed ancestor import,
>     let that grant visibility to entities that would not be visible
>     otherwise, with a warning attached to the permission.

... to USES of that permission.

> --- a/compiler/build_eqv_maps.m
> +++ b/compiler/build_eqv_maps.m
> @@ -122,7 +122,8 @@ build_eqv_maps_in_ancestor_int_spec(AncestorIntSpec,
>  
>  build_eqv_maps_in_direct_int1_spec(DirectIntSpec,
>          !TypeEqvMap, !InstEqvMap) :-
> -    DirectIntSpec = direct_int1(ParseTreeInt1, ReadWhy1),
> +    DirectIntSpec = direct_int1(ParseTreeInt1, ReadWhy1,
> +        _MaybeShadowedReadWhy1),
>      build_eqv_maps_in_parse_tree_int1(ReadWhy1, ParseTreeInt1,
>          !TypeEqvMap, !InstEqvMap).

About ignoring the second ReadWhy: what is the correctness argument
for this being safe to do? Please add a comment describing it.

The same question also arises for building eqv_maps in other parse trees
just below.

> --- a/compiler/d_file_deps.m
> +++ b/compiler/d_file_deps.m
> @@ -543,7 +543,8 @@ gather_fim_specs_in_ancestor_int_spec(AncestorIntSpec, !FIMSpecs) :-
>      set(fim_spec)::in, set(fim_spec)::out) is det.
>  
>  gather_fim_specs_in_direct_int1_spec(DirectInt1Spec, !FIMSpecs) :-
> -    DirectInt1Spec = direct_int1(ParseTreeInt1, _ReadWhy1),
> +    DirectInt1Spec = direct_int1(ParseTreeInt1, _ReadWhy1,
> +        _MaybeShadowedReadWhy1),
>      gather_fim_specs_in_parse_tree_int1(ParseTreeInt1, !FIMSpecs).

And here.

> --- a/compiler/module_qual.collect_mq_info.m
> +++ b/compiler/module_qual.collect_mq_info.m
> @@ -221,10 +243,10 @@ collect_mq_info_in_parse_tree_int0(ReadWhy0, ParseTreeInt0, !Info) :-
>          % the ancestor imported that mq_id in its INTERFACE or not.
>          % Since we don't know where that import was, this is a
>          % conservative approximation.
> -        IntPermInInt = may_use_in_int(may_be_unqualified),
> -        IntPermInImp = may_use_in_imp(may_be_unqualified),
> -        ImpPermInInt = may_use_in_int(may_be_unqualified),
> -        ImpPermInImp = may_use_in_imp(may_be_unqualified),
> +        IntPermInInt = perm_in_int(permitted, permitted),
> +        IntPermInImp = perm_in_imp(permitted, permitted),
> +        ImpPermInInt = perm_in_int(permitted, permitted),
> +        ImpPermInImp = perm_in_imp(permitted, permitted),

I think adding "_qual_unqual" as a suffix to perm_in_{int,imp} would
make the code easier to understand. Even if the reade remembers
that the two args for different qualifications, it is easy to forget
which is first :-( 

> --- a/compiler/module_qual.id_set.m
> +++ b/compiler/module_qual.id_set.m
> +update_permission(OldPermission, NewPermission) = Result :-
> +    (
> +        OldPermission = not_permitted,
> +        Result = NewPermission
> +    ;
> +        OldPermission = permitted,
> +        Result = permitted
> +    ;
> +        ( OldPermission = permitted_with_warning
> +        ; OldPermission = permitted_with_warning_shadowed
> +        ),
> +        % permitted_with_warning and permitted_with_warning_shadowed will be
> +        % used during a transition period. We do not expect to reach this
> +        % point.
> +        Result = OldPermission
>      ).

The reason why we don't expect to reach that switch arm is because we add
the permissions that include warnings *after* the ones that do not include them.

> @@ -529,53 +532,44 @@ add_matching_and_nearmiss_modules_int(InInt, FullyModuleQualified,
>      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),
> -                MaybeWarn = do_not_warn
> -            ;
> -                PermInInt = may_use_in_int_warn(NeedQual),
> -                MaybeWarn = warn_if_matching
> -            ),
> -            add_matching_and_nearmiss_modules_qual(FullyModuleQualified,
> -                NeedQual, ModuleName, MaybeWarn, !Matches, !QualMismatches)
> -        )
> +        PermInInt = perm_in_int(PermQual, PermUnqual)
>      ;
>          InInt = mq_not_used_in_interface,
> -        PermInImp = may_use_in_imp(NeedQual),
> -        add_matching_and_nearmiss_modules_qual(FullyModuleQualified,
> -            NeedQual, ModuleName, do_not_warn, !Matches, !QualMismatches)
> -    ).
> -
> -:- type maybe_warn_if_matching
> -    --->    do_not_warn
> -    ;       warn_if_matching.
> -
> -:- pred add_matching_and_nearmiss_modules_qual(bool::in, need_qualifier::in,
> -    module_name::in, maybe_warn_if_matching::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, MaybeWarn, !Matches, !QualMismatches) :-
> -    ( if
> -        ( FullyModuleQualified = yes
> -        ; NeedQual = may_be_unqualified
> -        )
> -    then
> +        PermInImp = perm_in_imp(PermQual, PermUnqual)
> +    ),
> +    (
> +        FullyModuleQualified = yes,
> +        Permission = PermQual,
> +        OtherPermission = PermUnqual
> +    ;
> +        FullyModuleQualified = no,
> +        Permission = PermUnqual,
> +        OtherPermission = PermQual
> +    ),
> +    (
> +        Permission = permitted,
> +        !:Matches = [match(ModuleName) | !.Matches]
> +    ;
>          (
> -            MaybeWarn = do_not_warn,
> -            MatchType = match(ModuleName)
> +            Permission = permitted_with_warning,
> +            WarnType = no_warn_shadowed_ancestor_import
>          ;
> -            MaybeWarn = warn_if_matching,
> -            MatchType = match_with_warning(ModuleName)
> +            Permission = permitted_with_warning_shadowed,
> +            WarnType = also_warn_shadowed_ancestor_import
>          ),
> -        !:Matches = [MatchType | !.Matches]
> -    else
> -        !:QualMismatches = [ModuleName | !.QualMismatches]
> +        !:Matches = [match_with_warning(ModuleName, WarnType) | !.Matches]
> +    ;
> +        Permission = not_permitted,
> +        (
> +            OtherPermission = not_permitted,
> +            !:IntMismatches = [ModuleName | !.IntMismatches]
> +        ;
> +            ( OtherPermission = permitted
> +            ; OtherPermission = permitted_with_warning
> +            ; OtherPermission = permitted_with_warning_shadowed
> +            ),
> +            !:QualMismatches = [ModuleName | !.QualMismatches]
> +        )
>      ).

I can't untangle the mess diff makes of this change; I will review it post-commit.
I will review the changes to the tests post-commit as well.

The diff is fine; thank you. Commit away.

Zoltan.


More information about the reviews mailing list