[m-rev.] for post-commit review: improve --warn-too-private-instances
Julien Fischer
jfischer at opturion.com
Wed Jul 23 12:45:40 AEST 2025
On Tue, 22 Jul 2025 at 22:57, Zoltan Somogyi <zoltan.somogyi at runbox.com> wrote:
> Improve --warn-too-private-instances.
>
> This implements some improvements to its idea from 2022 mar 19 on m-rev.
>
> compiler/check_typeclass.m:
> Consider an instance justifiably kept private if a constraint
> on the instance refers to a private type class or type constructor.
>
> Extend the test for what is considered "private" to also include
> classes and type constructs that are defined in private submodules
> of the current module.
>
> Extend the text of the warning to explain the reasoning behind it.
>
> compiler/options.m:
> Extend the documentation of the option to explain the reasoning
> behind it.
>
> tests/invalid/impure_method_impl.err_exp:
> tests/invalid/inconsistent_instances.err_exp:
> tests/invalid/method_impl.err_exp:
> tests/invalid/mpj_3.err_exp:
> tests/invalid/mpj_4.err_exp:
> tests/invalid/range_restrict.err_exp:
> tests/warnings/unused_interface_import.err_exp:
> Expect the new text for the warning.
>
> tests/warnings/help_text.err_exp:
> Expect the new help text.
>
> diff --git a/compiler/check_typeclass.m b/compiler/check_typeclass.m
> index aa4d2dc6b..b7df62e7c 100644
> --- a/compiler/check_typeclass.m
> +++ b/compiler/check_typeclass.m
> @@ -709,21 +732,124 @@ acc_type_ctors_in_type(Type, !TypeCtorSet) :-
> acc_type_ctors_in_type(SubType, !TypeCtorSet)
> ).
>
> -:- pred type_ctor_is_private(type_table::in, type_ctor::in) is semidet.
> +:- pred type_ctor_is_private(module_info::in, type_ctor::in) is semidet.
>
> -type_ctor_is_private(TypeTable, TypeCtor) :-
> +type_ctor_is_private(ModuleInfo, TypeCtor) :-
> + module_info_get_type_table(ModuleInfo, TypeTable),
> lookup_type_ctor_defn(TypeTable, TypeCtor, TypeDefn),
> get_type_defn_status(TypeDefn, TypeStatus),
> - type_status_defined_in_this_module(TypeStatus) = yes,
> - type_status_is_exported(TypeStatus) = no.
> + DefinedHere = type_status_defined_in_this_module(TypeStatus),
> + % A type_ctor is private if either ...
> + (
> + DefinedHere = yes,
> + % ... it is defined in this module, and not exported, ...
> + type_status_is_exported(TypeStatus) = no
> + ;
> + DefinedHere = no,
> + % ... or it is defined in a submodule nested inside this module,
> + % and that submodule is not visible from outside this module.
> + TypeCtor = type_ctor(TypeCtorSymName, _Arity),
> + TypeCtorSymName = qualified(TypeCtorModuleName, _),
> + module_name_is_private_submodule(ModuleInfo, TypeCtorModuleName)
> + ).
>
> -:- pred report_unnecessarily_private_instance(class_id::in, set(type_ctor)::in,
> +:- pred class_is_private(module_info::in, class_id::in) is semidet.
> +
> +class_is_private(ModuleInfo, ClassId) :-
> + module_info_get_class_table(ModuleInfo, ClassTable),
> + map.lookup(ClassTable, ClassId, ClassDefn),
> + ClassStatus = ClassDefn ^ classdefn_status,
> + DefinedHere = typeclass_status_defined_in_this_module(ClassStatus),
> + % A type_ctor is private if either ...
s/type_ctor/class
> + (
> + DefinedHere = yes,
> + % ... it is defined in this module, and not exported, ...
> + typeclass_status_is_exported(ClassStatus) = no
> + ;
> + DefinedHere = no,
> + % ... or it is defined in a submodule nested inside this module,
> + % and that submodule is not visible from outside this module.
> + ClassId = class_id(ClassSymName, _Arity),
> + ClassSymName = qualified(ClassModuleName, _),
> + module_name_is_private_submodule(ModuleInfo, ClassModuleName)
> + ).
> +
> + % module_name_is_private_submodule(ModuleInfo, QueryModuleName):
> + %
> + % Is QueryModuleName the name of a privaye submodule of the module
s/privaye/private/
> + % represented by ModuleInfo?
> + %
> +:- pred module_name_is_private_submodule(module_info::in, module_name::in)
> + is semidet.
> +
> +module_name_is_private_submodule(ModuleInfo, QueryModuleName) :-
> + module_info_get_name(ModuleInfo, ModuleName),
> + % XXX Should the code that tests whether QueryModuleName is a submodule
> + % of ModuleName be in mdbcomp/sym_name.m? is_same_module_or_submodule
> + % is already there, but it does not return LeftOverComponents.
> + ModuleNameComponents = sym_name_to_list(ModuleName),
> + QueryModuleNameComponents = sym_name_to_list(QueryModuleName),
> + remove_prefix(ModuleNameComponents, QueryModuleNameComponents,
> + LeftOverComponents),
> + LeftOverComponents = [HeadSubComponent | _TailSubComponents],
> + HeadSubModuleName = qualified(ModuleName, HeadSubComponent),
> + % What we want to do here is
> + % - to fail if any component in [HeadSubComponent | TailSubComponents]
> + % is in the interface section of ita parent, and conversely
> + % - to succeed only if all of them are in the implementation section.
> + % Unfortunately, while we can use IncludeMap to tell whether
> + % a *direct* submodule of ModuleName is visible outside ModuleName,
> + % we do not have the include_module_maps of ModuleName's submodules,
> + % and so we cannot tell whether an *indirect* submodule of ModuleName
> + % that is included in the interface section of ModuleName,
> + % is visible outside ModuleName. We have to guess. We always guess
> + % that it such indirect submodules are visible outside ModuleName,
Delete "it".
> + % because we prefer to miss generating a valid warning in this
> + % rare circumstance over generating an invalid warning.
> + module_info_get_include_module_map(ModuleInfo, IncludeMap),
> + map.lookup(IncludeMap, HeadSubModuleName, HeadSubIncludeInfo),
> + HeadSubIncludeInfo = include_module_info(HeadSubSection, _),
> + HeadSubSection = ms_implementation.
> +
> + % XXX Should this be library/list.m?
I think so.
> + %
> +:- pred remove_prefix(list(T)::in, list(T)::in, list(T)::out) is semidet.
> +
> +remove_prefix([], ListB, LeftOverB) :-
> + LeftOverB = ListB.
> +remove_prefix([HeadA | TailA], [HeadB | TailB], LeftOverB) :-
> + HeadA = HeadB,
> + remove_prefix(TailA, TailB, LeftOverB).
The rest looks fine.
Julien.
More information about the reviews
mailing list