%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2015-2024 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 implements the first pass of module_qual.m; it records
% what entities are available from which modules and with what permissions.
%

:- module parse_tree.module_qual.collect_mq_info.
:- interface.

:- import_module parse_tree.module_qual.mq_info.
:- import_module parse_tree.prog_parse_tree.

%---------------------------------------------------------------------------%

:- type int3_role
    --->    int3_as_src
    ;       int3_as_direct_int(read_why_int3).

    % Pass over the given parse tree collecting all defined module, type,
    % inst, mode and class ids, together with their permissions.
    %
:- pred collect_mq_info_in_parse_tree_module_src(parse_tree_module_src::in,
    mq_info::in, mq_info::out) is det.
:- pred collect_mq_info_in_ancestor_int_spec(ancestor_int_spec::in,
    mq_info::in, mq_info::out) is det.
:- pred collect_mq_info_in_direct_int1_spec(direct_int1_spec::in,
    mq_info::in, mq_info::out) is det.
:- pred collect_mq_info_in_direct_int3_spec(direct_int3_spec::in,
    mq_info::in, mq_info::out) is det.

:- pred collect_mq_info_in_parse_tree_int0(read_why_int0::in,
    parse_tree_int0::in, mq_info::in, mq_info::out) is det.
:- pred collect_mq_info_in_parse_tree_int3(int3_role::in,
    parse_tree_int3::in, mq_info::in, mq_info::out) is det.

%---------------------------------------------------------------------------%

:- implementation.

:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.item_util.
:- import_module parse_tree.module_qual.id_set.
:- import_module parse_tree.parse_sym_name.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_item.

:- import_module bool.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module one_or_more.
:- import_module one_or_more_map.
:- import_module require.
:- import_module set_tree234.
:- import_module term.

%---------------------------------------------------------------------------%

collect_mq_info_in_parse_tree_module_src(ParseTreeModuleSrc, !Info) :-
    IntPermInInt = may_use_in_int(may_be_unqualified),
    ImpPermInInt = may_not_use_in_int,
    PermInImp = may_use_in_imp(may_be_unqualified),
    IntPermissions = module_permissions(IntPermInInt, PermInImp),
    ImpPermissions = module_permissions(ImpPermInInt, PermInImp),

    ParseTreeModuleSrc = parse_tree_module_src(_ModuleName, _ModuleNameContext,
        InclMap, ImportUseMap,
        _IntFIMSpecMap, _ImpFIMSpecMap, _IntSelfFIMLangs, _ImpSelfFIMLangs,

        TypeCtorCheckedMap, InstCtorCheckedMap, ModeCtorCheckedMap,
        _TypeSpecs, _InstModeSpecs,

        IntTypeClasses, IntInstances, _IntPredDecls, _IntModeDecls,
        _IntDeclPragmas, _IntDeclMarkers, IntPromises, _IntBadPreds,

        ImpTypeClasses, ImpInstances, _ImpPredDecls, _ImpModeDecls,
        _ImpClauses, _ImpForeignProcs, _ImpForeignExportEnums,
        _ImpDeclPragmas, _ImpDeclMarkers, _ImpImplPragmas, _ImpImplMarkers,
        ImpPromises, _ImpInitialises, _ImpFinalises, _ImpMutables),

    mq_info_get_modules(!.Info, Modules0),
    map.foldl(collect_mq_info_in_included_module_info(IntPermissions),
        InclMap, Modules0, Modules),
    mq_info_set_modules(Modules, !Info),

    mq_info_get_imported_modules(!.Info, ImportedModules0),
    mq_info_get_as_yet_unused_interface_modules(!.Info, UnusedIntModules0),
    map.foldl2(collect_mq_info_in_src_avail_map_entry,
        ImportUseMap, ImportedModules0, ImportedModules,
        UnusedIntModules0, UnusedIntModules),
    mq_info_set_imported_modules(ImportedModules, !Info),
    mq_info_set_as_yet_unused_interface_modules(UnusedIntModules, !Info),

    mq_info_get_types(!.Info, Types0),
    type_ctor_checked_map_get_src_defns(TypeCtorCheckedMap,
        IntTypeDefns, ImpTypeDefns, _ImpForeignEnums),
    list.foldl(id_set_insert(IntPermissions),
        list.map(item_type_defn_info_to_mq_id, IntTypeDefns), Types0, Types1),
    list.foldl(id_set_insert(ImpPermissions),
        list.map(item_type_defn_info_to_mq_id, ImpTypeDefns), Types1, Types),
    mq_info_set_types(Types, !Info),

    mq_info_get_insts(!.Info, Insts0),
    inst_ctor_checked_map_get_src_defns(InstCtorCheckedMap,
        IntInstDefns, ImpInstDefns),
    list.foldl(id_set_insert(IntPermissions),
        list.map(item_inst_defn_info_to_mq_id, IntInstDefns), Insts0, Insts1),
    list.foldl(id_set_insert(ImpPermissions),
        list.map(item_inst_defn_info_to_mq_id, ImpInstDefns), Insts1, Insts),
    mq_info_set_insts(Insts, !Info),

    mq_info_get_modes(!.Info, Modes0),
    mode_ctor_checked_map_get_src_defns(ModeCtorCheckedMap,
        IntModeDefns, ImpModeDefns),
    list.foldl(id_set_insert(IntPermissions),
        list.map(item_mode_defn_info_to_mq_id, IntModeDefns), Modes0, Modes1),
    list.foldl(id_set_insert(ImpPermissions),
        list.map(item_mode_defn_info_to_mq_id, ImpModeDefns), Modes1, Modes),
    mq_info_set_modes(Modes, !Info),

    list.foldl(collect_mq_info_in_item_typeclass(IntPermissions),
        IntTypeClasses, !Info),
    list.foldl(collect_mq_info_in_item_typeclass(ImpPermissions),
        ImpTypeClasses, !Info),
    list.foldl(collect_mq_info_in_item_instance, IntInstances, !Info),
    list.foldl(collect_mq_info_in_item_instance, ImpInstances, !Info),
    list.foldl(collect_mq_info_in_item_promise(mq_used_in_interface),
        IntPromises, !Info),
    list.foldl(collect_mq_info_in_item_promise(mq_not_used_in_interface),
        ImpPromises, !Info).

:- pred collect_mq_info_in_src_avail_map_entry(
    module_name::in, maybe_implicit_import_and_or_use::in,
    set_tree234(module_name)::in, set_tree234(module_name)::out,
    module_names_contexts::in, module_names_contexts::out) is det.

collect_mq_info_in_src_avail_map_entry(ModuleName, MaybeImplicit,
        !ImportedModules, !UnusedIntModules) :-
    set_tree234.insert(ModuleName, !ImportedModules),
    ( if
        (
            MaybeImplicit = implicit_avail(_Implicit, MaybeSection),
            MaybeSection = yes(Section)
        ;
            MaybeImplicit = explicit_avail(Section)
        ),
        require_complete_switch [Section]
        (
            Section = int_import(IntContext)
        ;
            Section = int_use(IntContext)
        ;
            Section = int_use_imp_import(IntContext, _ImpContext)
        ;
            ( Section = imp_import(_ImpContext)
            ; Section = imp_use(_ImpContext)
            ),
            fail
        )
    then
        % Most of the time, ModuleName does not occur in !.UnusedIntModules.
        % We therefore try the insertion first, and only if the insertion
        % fails do we look up and update the existing entry (OldContexts)
        % that caused that failure.
        IntContexts = one_or_more(IntContext, []),
        map.search_insert(ModuleName, IntContexts, MaybeOldContexts,
            !UnusedIntModules),
        (
            MaybeOldContexts = no
        ;
            MaybeOldContexts = yes(OldContexts),
            % This is quadratic in the number of import_module/use_module
            % declarations we see for the same module name in the interface,
            % but this should not matter, because even two such declarations
            % would be very rare, and three or more would virtually never occur
            % in real life code (as opposed to in code that was specifically
            % constructed as a stress test).
            NewContexts = OldContexts ++ IntContexts,
            map.det_update(ModuleName, NewContexts, !UnusedIntModules)
        )
    else
        % Either there is no explicit import or use of ModuleName at all,
        % or there is such an import or use, but only in the implementation
        % section.
        true
    ).

%---------------------------------------------------------------------------%

collect_mq_info_in_ancestor_int_spec(AncestorIntSpec, !Info) :-
    AncestorIntSpec = ancestor_int0(ParseTreeInt0, ReadWhy0),
    collect_mq_info_in_parse_tree_int0(ReadWhy0, ParseTreeInt0, !Info).

collect_mq_info_in_direct_int1_spec(DirectInt1Spec, !Info) :-
    DirectInt1Spec = direct_int1(ParseTreeInt1, ReadWhy1),
    collect_mq_info_in_parse_tree_int1(ReadWhy1, ParseTreeInt1, !Info).

collect_mq_info_in_direct_int3_spec(DirectInt3Spec, !Info) :-
    DirectInt3Spec = direct_int3(ParseTreeInt3, ReadWhy3),
    Role = int3_as_direct_int(ReadWhy3),
    collect_mq_info_in_parse_tree_int3(Role, ParseTreeInt3, !Info).

%---------------------------------------------------------------------------%

collect_mq_info_in_parse_tree_int0(ReadWhy0, ParseTreeInt0, !Info) :-
    (
        ReadWhy0 = rwi0_section,
        % XXX Whether this module's interface can use an mq_id
        % that was imported by an ancestor should depend on whether
        % 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),

        IntPermissions = module_permissions(IntPermInInt, IntPermInImp),
        ImpPermissions = module_permissions(ImpPermInInt, ImpPermInImp)
    ;
        ReadWhy0 = rwi0_opt,
        % Since we do not collect module qual info for int_for_opt_specs,
        % we should never encounter this value of ReadWhy0.
        unexpected($pred, "rwi0_opt")
    ),

    ParseTreeInt0 = parse_tree_int0(_ModuleName, _ModuleNameContext,
        _MaybeVersionNumbers, InclMap,
        ImportUseMap, _IntFIMSpecs, _ImpFIMSpecs,
        TypeCtorCheckedMap, InstCtorCheckedMap, ModeCtorCheckedMap,
        IntTypeClasses, IntInstances, _IntPredDecls, _IntModeDecls,
        _IntDeclPragmas, _IntDeclMarkers, IntPromises,
        ImpTypeClasses, ImpInstances, _ImpPredDecls, _ImpModeDecls,
        _ImpDeclPragmas, _ImpDeclMarkers, ImpPromises),

    mq_info_get_modules(!.Info, Modules0),
    map.foldl(collect_mq_info_in_included_module_info(IntPermissions),
        InclMap, Modules0, Modules),
    mq_info_set_modules(Modules, !Info),

    mq_info_get_imported_modules(!.Info, ImportedModules0),
    list.foldl(collect_mq_info_in_int0_import_or_use, map.keys(ImportUseMap),
        ImportedModules0, ImportedModules),
    mq_info_set_imported_modules(ImportedModules, !Info),

    mq_info_get_types(!.Info, Types0),
    map.foldl(
        collect_mq_info_in_int_imp_type_defn(IntPermissions, ImpPermissions),
        TypeCtorCheckedMap, Types0, Types),
    mq_info_set_types(Types, !Info),

    mq_info_get_insts(!.Info, Insts0),
    map.foldl(
        collect_mq_info_in_int_imp_inst_defn(IntPermissions, ImpPermissions),
        InstCtorCheckedMap, Insts0, Insts),
    mq_info_set_insts(Insts, !Info),

    mq_info_get_modes(!.Info, Modes0),
    map.foldl(
        collect_mq_info_in_int_imp_mode_defn(IntPermissions, ImpPermissions),
        ModeCtorCheckedMap, Modes0, Modes),
    mq_info_set_modes(Modes, !Info),

    list.foldl(collect_mq_info_in_item_typeclass(IntPermissions),
        IntTypeClasses, !Info),
    list.foldl(collect_mq_info_in_item_typeclass(ImpPermissions),
        ImpTypeClasses, !Info),
    list.foldl(collect_mq_info_in_item_instance, coerce(IntInstances), !Info),
    list.foldl(collect_mq_info_in_item_instance, coerce(ImpInstances), !Info),
    list.foldl(collect_mq_info_in_item_promise(mq_used_in_interface),
        IntPromises, !Info),
    list.foldl(collect_mq_info_in_item_promise(mq_not_used_in_interface),
        ImpPromises, !Info).

%---------------------------------------------------------------------------%

:- pred collect_mq_info_in_parse_tree_int1(read_why_int1::in,
    parse_tree_int1::in, mq_info::in, mq_info::out) is det.

collect_mq_info_in_parse_tree_int1(ReadWhy1, ParseTreeInt1, !Info) :-
    (
        ReadWhy1 = rwi1_int_import,
        IntPermInInt = may_use_in_int(may_be_unqualified),
        IntPermInImp = may_use_in_imp(may_be_unqualified)
    ;
        ReadWhy1 = rwi1_imp_import,
        IntPermInInt = may_not_use_in_int,
        IntPermInImp = may_use_in_imp(may_be_unqualified)
    ;
        ReadWhy1 = rwi1_int_use,
        IntPermInInt = may_use_in_int(must_be_qualified),
        IntPermInImp = may_use_in_imp(must_be_qualified)
    ;
        ReadWhy1 = rwi1_imp_use,
        IntPermInInt = may_not_use_in_int,
        IntPermInImp = may_use_in_imp(must_be_qualified)
    ;
        ReadWhy1 = rwi1_int_use_imp_import,
        IntPermInInt = may_use_in_int(must_be_qualified),
        IntPermInImp = may_use_in_imp(may_be_unqualified)
    ;
        ReadWhy1 = rwi1_opt,
        % Since we do not collect module qual info for int_for_opt_specs,
        % we should never encounter this value of ReadWhy1.
        unexpected($pred, "rwi1_opt")
    ;
        ReadWhy1 = rwi1_type_repn,
        % Since we do not collect module qual info for type_repn_specs,
        % we should never encounter this value of ReadWhy1.
        unexpected($pred, "rwi1_opt")
    ),
    IntPermissions = module_permissions(IntPermInInt, IntPermInImp),
    % The implementation section of a .int1 file is abstract imported,
    % which means we have no permission for any item defined there.
    % We therefore do not need any ImpPermissions.

    ParseTreeInt1 = parse_tree_int1(_ModuleName, _ModuleNameContext,
        _MaybeVersionNumbers, InclMap,
        _ImportUseMap, _IntFIMSpecs, _ImpFIMSpecs,
        TypeCheckedMap, InstCheckedMap, ModeCheckedMap,
        IntTypeClasses, IntInstances, _IntPredDecls, _IntModeDecls,
        _IntDeclPragmas, _IntDeclMarkers, IntPromises, _IntTypeRepnMap,
        _ImpTypeClasses),

    mq_info_get_modules(!.Info, Modules0),
    map.foldl(collect_mq_info_in_included_module_info(IntPermissions),
        InclMap, Modules0, Modules),
    mq_info_set_modules(Modules, !Info),

    mq_info_get_types(!.Info, Types0),
    map.foldl(collect_mq_info_in_int_type_defn(IntPermissions),
        TypeCheckedMap, Types0, Types),
    mq_info_set_types(Types, !Info),

    mq_info_get_insts(!.Info, Insts0),
    map.foldl(collect_mq_info_in_int_inst_defn(IntPermissions),
        InstCheckedMap, Insts0, Insts),
    mq_info_set_insts(Insts, !Info),

    mq_info_get_modes(!.Info, Modes0),
    map.foldl(collect_mq_info_in_int_mode_defn(IntPermissions),
        ModeCheckedMap, Modes0, Modes),
    mq_info_set_modes(Modes, !Info),

    list.foldl(collect_mq_info_in_item_typeclass(IntPermissions),
        IntTypeClasses, !Info),
    list.foldl(collect_mq_info_in_item_instance, coerce(IntInstances), !Info),
    list.foldl(collect_mq_info_in_item_promise(mq_used_in_interface),
        IntPromises, !Info).

%---------------------------------------------------------------------------%

:- pred collect_mq_info_in_int_imp_type_defn(
    module_permissions::in, module_permissions::in,
    type_ctor::in, type_ctor_checked_defn::in,
    type_id_set::in, type_id_set::out) is det.

collect_mq_info_in_int_imp_type_defn(IntPermissions, ImpPermissions,
        TypeCtor, CheckedDefn, !Types) :-
    TypeId = type_ctor_to_mq_id(TypeCtor),
    (
        CheckedDefn = checked_defn_solver(SolverDefn, _),
        (
            SolverDefn = solver_type_abstract(AbsStatus, _),
            (
                AbsStatus = abstract_solver_type_exported,
                id_set_insert(IntPermissions,  TypeId, !Types)
            ;
                AbsStatus = abstract_solver_type_private,
                id_set_insert(ImpPermissions,  TypeId, !Types)
            )
        ;
            SolverDefn = solver_type_full(MaybeAbsDefn, _),
            (
                MaybeAbsDefn = yes(_),
                id_set_insert(IntPermissions,  TypeId, !Types)
            ;
                MaybeAbsDefn = no,
                id_set_insert(ImpPermissions,  TypeId, !Types)
            )
        )
    ;
        CheckedDefn = checked_defn_std(StdDefn, _),
        (
            StdDefn = std_mer_type_eqv(EqvStatus, _),
            (
                ( EqvStatus = std_eqv_type_mer_exported
                ; EqvStatus = std_eqv_type_abstract_exported
                ),
                id_set_insert(IntPermissions,  TypeId, !Types)
            ;
                EqvStatus = std_eqv_type_all_private,
                id_set_insert(ImpPermissions,  TypeId, !Types)
            )
        ;
            StdDefn = std_mer_type_subtype(SubStatus, _),
            (
                ( SubStatus = std_sub_type_mer_exported
                ; SubStatus = std_sub_type_abstract_exported
                ),
                id_set_insert(IntPermissions,  TypeId, !Types)
            ;
                SubStatus = std_sub_type_all_private,
                id_set_insert(ImpPermissions,  TypeId, !Types)
            )
        ;
            (
                StdDefn =
                    std_mer_type_du_all_plain_constants(DuStatus, _, _, _, _)
            ;
                StdDefn =
                    std_mer_type_du_not_all_plain_constants(DuStatus, _, _)
            ),
            (
                ( DuStatus = std_du_type_mer_ft_exported
                ; DuStatus = std_du_type_mer_exported
                ; DuStatus = std_du_type_abstract_exported
                ),
                id_set_insert(IntPermissions,  TypeId, !Types)
            ;
                DuStatus = std_du_type_all_private,
                id_set_insert(ImpPermissions,  TypeId, !Types)
            )
        ;
            StdDefn = std_mer_type_abstract(AbsStatus, _, _),
            (
                ( AbsStatus = std_abs_type_ft_exported
                ; AbsStatus = std_abs_type_abstract_exported
                ),
                id_set_insert(IntPermissions,  TypeId, !Types)
            ;
                AbsStatus = std_abs_type_all_private,
                id_set_insert(ImpPermissions,  TypeId, !Types)
            )
        )
    ).

:- pred collect_mq_info_in_int_type_defn(module_permissions::in,
    type_ctor::in, type_ctor_checked_defn::in,
    type_id_set::in, type_id_set::out) is det.

collect_mq_info_in_int_type_defn(IntPermissions, TypeCtor, CheckedDefn,
        !Types) :-
    TypeId = type_ctor_to_mq_id(TypeCtor),
    (
        CheckedDefn = checked_defn_solver(SolverDefn, _),
        (
            SolverDefn = solver_type_abstract(AbsStatus, _),
            (
                AbsStatus = abstract_solver_type_exported,
                id_set_insert(IntPermissions,  TypeId, !Types)
            ;
                AbsStatus = abstract_solver_type_private
            )
        ;
            SolverDefn = solver_type_full(MaybeAbsDefn, _),
            (
                MaybeAbsDefn = yes(_),
                id_set_insert(IntPermissions,  TypeId, !Types)
            ;
                MaybeAbsDefn = no
            )
        )
    ;
        CheckedDefn = checked_defn_std(StdDefn, _),
        (
            StdDefn = std_mer_type_eqv(EqvStatus, _),
            (
                ( EqvStatus = std_eqv_type_mer_exported
                ; EqvStatus = std_eqv_type_abstract_exported
                ),
                id_set_insert(IntPermissions,  TypeId, !Types)
            ;
                EqvStatus = std_eqv_type_all_private
            )
        ;
            StdDefn = std_mer_type_subtype(SubStatus, _),
            (
                ( SubStatus = std_sub_type_mer_exported
                ; SubStatus = std_sub_type_abstract_exported
                ),
                id_set_insert(IntPermissions,  TypeId, !Types)
            ;
                SubStatus = std_sub_type_all_private
            )
        ;
            (
                StdDefn =
                    std_mer_type_du_all_plain_constants(DuStatus, _, _, _, _)
            ;
                StdDefn =
                    std_mer_type_du_not_all_plain_constants(DuStatus, _, _)
            ),
            (
                ( DuStatus = std_du_type_mer_ft_exported
                ; DuStatus = std_du_type_mer_exported
                ; DuStatus = std_du_type_abstract_exported
                ),
                id_set_insert(IntPermissions,  TypeId, !Types)
            ;
                DuStatus = std_du_type_all_private
            )
        ;
            StdDefn = std_mer_type_abstract(AbsStatus, _, _),
            (
                ( AbsStatus = std_abs_type_ft_exported
                ; AbsStatus = std_abs_type_abstract_exported
                ),
                id_set_insert(IntPermissions,  TypeId, !Types)
            ;
                AbsStatus = std_abs_type_all_private
            )
        )
    ).

:- pred collect_mq_info_in_int_imp_inst_defn(
    module_permissions::in, module_permissions::in,
    inst_ctor::in, inst_ctor_checked_defn::in,
    inst_id_set::in, inst_id_set::out) is det.

collect_mq_info_in_int_imp_inst_defn(IntPermissions, ImpPermissions,
        InstCtor, CheckedDefn, !Insts) :-
    CheckedDefn = checked_defn_inst(StdInstDefn, _),
    StdInstDefn = std_inst_defn(Status, _),
    (
        ( Status = std_inst_exported
        ; Status = std_inst_abstract_exported
        ),
        id_set_insert(IntPermissions,  inst_ctor_to_mq_id(InstCtor), !Insts)
    ;
        Status = std_inst_all_private,
        id_set_insert(ImpPermissions,  inst_ctor_to_mq_id(InstCtor), !Insts)
    ).

:- pred collect_mq_info_in_int_inst_defn(module_permissions::in,
    inst_ctor::in, inst_ctor_checked_defn::in,
    inst_id_set::in, inst_id_set::out) is det.

collect_mq_info_in_int_inst_defn(IntPermissions, InstCtor, CheckedDefn,
        !Insts) :-
    CheckedDefn = checked_defn_inst(StdInstDefn, _),
    StdInstDefn = std_inst_defn(Status, _),
    (
        ( Status = std_inst_exported
        ; Status = std_inst_abstract_exported
        ),
        id_set_insert(IntPermissions,  inst_ctor_to_mq_id(InstCtor), !Insts)
    ;
        Status = std_inst_all_private
    ).

:- pred collect_mq_info_in_int_imp_mode_defn(
    module_permissions::in, module_permissions::in,
    mode_ctor::in, mode_ctor_checked_defn::in,
    mode_id_set::in, mode_id_set::out) is det.

collect_mq_info_in_int_imp_mode_defn(IntPermissions, ImpPermissions,
        ModeCtor, CheckedDefn, !Modes) :-
    CheckedDefn = checked_defn_mode(StdModeDefn, _),
    StdModeDefn = std_mode_defn(Status, _),
    (
        ( Status = std_mode_exported
        ; Status = std_mode_abstract_exported
        ),
        id_set_insert(IntPermissions,  mode_ctor_to_mq_id(ModeCtor), !Modes)
    ;
        Status = std_mode_all_private,
        id_set_insert(ImpPermissions,  mode_ctor_to_mq_id(ModeCtor), !Modes)
    ).

:- pred collect_mq_info_in_int_mode_defn(module_permissions::in,
    mode_ctor::in, mode_ctor_checked_defn::in,
    mode_id_set::in, mode_id_set::out) is det.

collect_mq_info_in_int_mode_defn(IntPermissions, ModeCtor, CheckedDefn,
        !Modes) :-
    CheckedDefn = checked_defn_mode(StdModeDefn, _),
    StdModeDefn = std_mode_defn(Status, _),
    (
        ( Status = std_mode_exported
        ; Status = std_mode_abstract_exported
        ),
        id_set_insert(IntPermissions,  mode_ctor_to_mq_id(ModeCtor), !Modes)
    ;
        Status = std_mode_all_private
    ).

%---------------------------------------------------------------------------%

collect_mq_info_in_parse_tree_int3(Role, ParseTreeInt3, !Info) :-
    (
        Role = int3_as_src,
        PermInInt = may_use_in_int(may_be_unqualified),
        PermInImp = may_use_in_imp(may_be_unqualified)
    ;
        Role = int3_as_direct_int(ReadWhy3),
        (
            ( ReadWhy3 = rwi3_direct_ancestor_import
            ; ReadWhy3 = rwi3_direct_int_import
            ),
            PermInInt = may_use_in_int(may_be_unqualified),
            PermInImp = may_use_in_imp(may_be_unqualified)
        ;
            ( ReadWhy3 = rwi3_direct_ancestor_use
            ; ReadWhy3 = rwi3_direct_int_use
            ; ReadWhy3 = rwi3_indirect_int_use
            ),
            PermInInt = may_use_in_int(must_be_qualified),
            PermInImp = may_use_in_imp(must_be_qualified)
        ;
            ReadWhy3 = rwi3_direct_imp_import,
            PermInInt = may_not_use_in_int,
            PermInImp = may_use_in_imp(may_be_unqualified)
        ;
            ( ReadWhy3 = rwi3_direct_imp_use
            ; ReadWhy3 = rwi3_indirect_imp_use
            ),
            PermInInt = may_not_use_in_int,
            PermInImp = may_use_in_imp(must_be_qualified)
        ;
            ReadWhy3 = rwi3_direct_int_use_imp_import,
            PermInInt = may_use_in_int(must_be_qualified),
            PermInImp = may_use_in_imp(may_be_unqualified)
        )
    ),
    Permissions = module_permissions(PermInInt, PermInImp),

    ParseTreeInt3 = parse_tree_int3(_ModuleName, _ModuleNameContext,
        IntInclMap, IntImportMap,
        IntTypeDefnMap, IntInstDefnMap, IntModeDefnMap,
        IntTypeClasses, IntInstances, _IntTypeRepns),

    mq_info_get_modules(!.Info, Modules0),
    list.foldl(collect_mq_info_in_int_incl_context(Permissions),
        map.keys(IntInclMap), Modules0, Modules),
    mq_info_set_modules(Modules, !Info),

    mq_info_get_imported_modules(!.Info, ImportedModules0),
    list.foldl(collect_mq_info_in_int3_import, map.keys(IntImportMap),
        ImportedModules0, ImportedModules),
    mq_info_set_imported_modules(ImportedModules, !Info),

    mq_info_get_types(!.Info, Types0),
    TypeIds = list.map(type_ctor_to_mq_id, map.keys(IntTypeDefnMap)),
    list.foldl(id_set_insert(Permissions), TypeIds, Types0, Types),
    mq_info_set_types(Types, !Info),

    mq_info_get_insts(!.Info, Insts0),
    InstIds = list.map(inst_ctor_to_mq_id, map.keys(IntInstDefnMap)),
    list.foldl(id_set_insert(Permissions), InstIds, Insts0, Insts),
    mq_info_set_insts(Insts, !Info),

    mq_info_get_modes(!.Info, Modes0),
    ModeIds = list.map(mode_ctor_to_mq_id, map.keys(IntModeDefnMap)),
    list.foldl(id_set_insert(Permissions), ModeIds, Modes0, Modes),
    mq_info_set_modes(Modes, !Info),

    list.foldl(collect_mq_info_in_item_typeclass(Permissions),
        coerce(IntTypeClasses), !Info),
    list.foldl(collect_mq_info_in_item_instance, coerce(IntInstances), !Info).

%---------------------------------------------------------------------------%

:- func type_ctor_to_mq_id(type_ctor) = mq_id.

type_ctor_to_mq_id(TypeCtor) = Id :-
    TypeCtor = type_ctor(SymName, Arity),
    Id = mq_id(SymName, Arity).

:- func inst_ctor_to_mq_id(inst_ctor) = mq_id.

inst_ctor_to_mq_id(InstCtor) = Id :-
    InstCtor = inst_ctor(SymName, Arity),
    Id = mq_id(SymName, Arity).

:- func mode_ctor_to_mq_id(mode_ctor) = mq_id.

mode_ctor_to_mq_id(ModeCtor) = Id :-
    ModeCtor = mode_ctor(SymName, Arity),
    Id = mq_id(SymName, Arity).

%---------------------------------------------------------------------------%

:- pred collect_mq_info_in_included_module_info(module_permissions::in,
    module_name::in, include_module_info::in,
    module_id_set::in, module_id_set::out) is det.

collect_mq_info_in_included_module_info(IntPermissions, ModuleName, InclInfo,
        !Modules) :-
    InclInfo = include_module_info(Section, _Context),
    % XXX Why do we test Section if we do the same thing for both int and imp?
    (
        Section = ms_interface,
        Arity = 0,
        id_set_insert(IntPermissions, mq_id(ModuleName, Arity), !Modules)
    ;
        Section = ms_implementation,
        Arity = 0,
        id_set_insert(IntPermissions, mq_id(ModuleName, Arity), !Modules)
    ).

:- pred collect_mq_info_in_int_incl_context(module_permissions::in,
    module_name::in, module_id_set::in, module_id_set::out) is det.

collect_mq_info_in_int_incl_context(IntPermissions, ModuleName, !Modules) :-
    Arity = 0,
    id_set_insert(IntPermissions, mq_id(ModuleName, Arity), !Modules).

:- pred collect_mq_info_in_int0_import_or_use(module_name::in,
    set_tree234(module_name)::in, set_tree234(module_name)::out) is det.

collect_mq_info_in_int0_import_or_use(ModuleName, !ImportedModules) :-
    set_tree234.insert(ModuleName, !ImportedModules).

:- pred collect_mq_info_in_int3_import(module_name::in,
    set_tree234(module_name)::in, set_tree234(module_name)::out) is det.

collect_mq_info_in_int3_import(ModuleName, !ImportedModules) :-
    set_tree234.insert(ModuleName, !ImportedModules).

:- func item_type_defn_info_to_mq_id(item_type_defn_info) = mq_id.

item_type_defn_info_to_mq_id(ItemTypeDefn) = MQId :-
    ItemTypeDefn = item_type_defn_info(SymName, Params, _, _, _, _),
    MQId = mq_id(SymName, list.length(Params)).

:- func item_inst_defn_info_to_mq_id(item_inst_defn_info) = mq_id.

item_inst_defn_info_to_mq_id(ItemInstDefn) = MQId :-
    ItemInstDefn = item_inst_defn_info(SymName, Params, _, _, _, _, _),
    MQId = mq_id(SymName, list.length(Params)).

:- func item_mode_defn_info_to_mq_id(item_mode_defn_info) = mq_id.

item_mode_defn_info_to_mq_id(ItemModeDefn) = MQId :-
    ItemModeDefn = item_mode_defn_info(SymName, Params, _, _, _, _),
    MQId = mq_id(SymName, list.length(Params)).

:- pred collect_mq_info_in_item_typeclass(module_permissions::in,
    item_typeclass_info::in, mq_info::in, mq_info::out) is det.

collect_mq_info_in_item_typeclass(Permissions, ItemTypeClass, !Info) :-
    ItemTypeClass = item_typeclass_info(SymName, Params, _, _, _, _, _, _),
    list.length(Params, Arity),
    mq_info_get_classes(!.Info, Classes0),
    id_set_insert(Permissions, mq_id(SymName, Arity), Classes0, Classes),
    mq_info_set_classes(Classes, !Info).

:- pred collect_mq_info_in_item_instance(item_instance_info::in,
    mq_info::in, mq_info::out) is det.

collect_mq_info_in_item_instance(ItemInstance, !Info) :-
    InstanceModule = ItemInstance ^ ci_module_containing_instance,
    mq_info_get_imported_instance_modules(!.Info, ImportedInstanceModules0),
    set_tree234.insert(InstanceModule,
        ImportedInstanceModules0, ImportedInstanceModules),
    mq_info_set_imported_instance_modules(ImportedInstanceModules, !Info).

:- pred collect_mq_info_in_item_promise(mq_in_interface::in,
    item_promise_info::in, mq_info::in, mq_info::out) is det.

collect_mq_info_in_item_promise(InInt, ItemPromise, !Info) :-
    ItemPromise = item_promise_info(_PromiseType, Goal, _ProgVarSet,
        _UnivVars, _Context, _SeqNum),
    collect_used_modules_in_promise_goal(Goal,
        set_tree234.init, UsedModuleNames, no, FoundUnqual),
    (
        FoundUnqual = no,
        set_tree234.fold(mq_info_set_module_used(InInt),
            UsedModuleNames, !Info)
    ;
        % Any unqualified symbol in the promise might come from *any* of
        % the imported modules. There is no way for us to tell which ones,
        % so we conservatively assume that it uses *all* of them.
        FoundUnqual = yes,
        map.init(UnusedModules),
        mq_info_set_as_yet_unused_interface_modules(UnusedModules, !Info)
    ).

%---------------------%

    % Scan Goal. Add the set of module names found in the qualified symbols
    % in Goal to !UsedModuleNames. If there exists a single unqualified symbol,
    % in Goal, set !Success to no.
    %
:- pred collect_used_modules_in_promise_goal(goal::in,
    set_tree234(module_name)::in, set_tree234(module_name)::out,
    bool::in, bool::out) is det.

collect_used_modules_in_promise_goal(Goal, !UsedModuleNames, !FoundUnqual) :-
    (
        ( Goal = conj_expr(_, SubGoalA, SubGoalsB)
        ; Goal = par_conj_expr(_, SubGoalA, SubGoalsB)
        ),
        collect_used_modules_in_promise_goal(SubGoalA,
            !UsedModuleNames, !FoundUnqual),
        collect_used_modules_in_promise_goals(SubGoalsB,
            !UsedModuleNames, !FoundUnqual)
    ;
        ( Goal = implies_expr(_, SubGoalA, SubGoalB)
        ; Goal = equivalent_expr(_, SubGoalA, SubGoalB)
        ),
        collect_used_modules_in_promise_goal(SubGoalA,
            !UsedModuleNames, !FoundUnqual),
        collect_used_modules_in_promise_goal(SubGoalB,
            !UsedModuleNames, !FoundUnqual)
    ;
        Goal = disj_expr(_, SubGoal1, SubGoal2, SubGoals),
        collect_used_modules_in_promise_goal(SubGoal1,
            !UsedModuleNames, !FoundUnqual),
        collect_used_modules_in_promise_goal(SubGoal2,
            !UsedModuleNames, !FoundUnqual),
        collect_used_modules_in_promise_goals(SubGoals,
            !UsedModuleNames, !FoundUnqual)
    ;
        ( Goal = true_expr(_)
        ; Goal = fail_expr(_)
        )
    ;
        ( Goal = not_expr(_, SubGoal)
        ; Goal = quant_expr(_, _, _, _, SubGoal)
        ; Goal = promise_purity_expr(_, _, SubGoal)
        ; Goal = promise_equivalent_solutions_expr(_, _, _, _, _, SubGoal)
        ; Goal = promise_equivalent_solution_sets_expr(_, _, _, _, _, SubGoal)
        ; Goal = promise_equivalent_solution_arbitrary_expr(_, _, _, _, _,
            SubGoal)
        ; Goal = require_detism_expr(_, _, SubGoal)
        ; Goal = require_complete_switch_expr(_, _, SubGoal)
        ; Goal = require_switch_arms_detism_expr(_, _, _, SubGoal)
        ; Goal = trace_expr(_, _, _, _, _, SubGoal)
        ; Goal = disable_warnings_expr(_, _, _, SubGoal)
        ),
        collect_used_modules_in_promise_goal(SubGoal,
            !UsedModuleNames, !FoundUnqual)
    ;
        Goal = try_expr(_, _, SubGoal, ThenGoal, MaybeElseGoal, Catches,
            MaybeCatchAny),
        collect_used_modules_in_promise_goal(SubGoal,
            !UsedModuleNames, !FoundUnqual),
        collect_used_modules_in_promise_goal(ThenGoal,
            !UsedModuleNames, !FoundUnqual),
        (
            MaybeElseGoal = no
        ;
            MaybeElseGoal = yes(ElseGoal),
            collect_used_modules_in_promise_goal(ElseGoal,
                !UsedModuleNames, !FoundUnqual)
        ),
        list.foldl2(collect_used_modules_in_promise_catch, Catches,
            !UsedModuleNames, !FoundUnqual),
        (
            MaybeCatchAny = no
        ;
            MaybeCatchAny = yes(catch_any_expr(_, CatchAnyGoal)),
            collect_used_modules_in_promise_goal(CatchAnyGoal,
                !UsedModuleNames, !FoundUnqual)
        )
    ;
        Goal = atomic_expr(_, _, _, _, MainGoal, OrElseGoals),
        collect_used_modules_in_promise_goal(MainGoal,
            !UsedModuleNames, !FoundUnqual),
        collect_used_modules_in_promise_goals(OrElseGoals,
            !UsedModuleNames, !FoundUnqual)
    ;
        Goal = if_then_else_expr(_, _, _, CondGoal, ThenGoal, ElseGoal),
        collect_used_modules_in_promise_goal(CondGoal,
            !UsedModuleNames, !FoundUnqual),
        collect_used_modules_in_promise_goal(ThenGoal,
            !UsedModuleNames, !FoundUnqual),
        collect_used_modules_in_promise_goal(ElseGoal,
            !UsedModuleNames, !FoundUnqual)
    ;
        Goal = event_expr(_, _Name, ArgTerms0),
        list.map(term.coerce, ArgTerms0, ArgTerms),
        collect_used_modules_in_terms(ArgTerms, !UsedModuleNames, !FoundUnqual)
    ;
        Goal = call_expr(_, SymName, ArgTerms0, _Purity),
        (
            SymName = qualified(ModuleName, _),
            set_tree234.insert(ModuleName, !UsedModuleNames)
        ;
            SymName = unqualified(_),
            !:FoundUnqual = yes
        ),
        list.map(term.coerce, ArgTerms0, ArgTerms),
        collect_used_modules_in_terms(ArgTerms, !UsedModuleNames, !FoundUnqual)
    ;
        Goal = unify_expr(_, LHS0, RHS0, _Purity),
        term.coerce(LHS0, LHS),
        term.coerce(RHS0, RHS),
        collect_used_modules_in_term(LHS, !UsedModuleNames, !FoundUnqual),
        collect_used_modules_in_term(RHS, !UsedModuleNames, !FoundUnqual)
    ).

    % Performs collect_used_modules_in_promise_goal on a list of goals.
    %
:- pred collect_used_modules_in_promise_goals(list(goal)::in,
    set_tree234(module_name)::in, set_tree234(module_name)::out,
    bool::in, bool::out) is det.

collect_used_modules_in_promise_goals([], !UsedModuleNames, !FoundUnqual).
collect_used_modules_in_promise_goals([Goal | Goals],
        !UsedModuleNames, !FoundUnqual) :-
    collect_used_modules_in_promise_goal(Goal,
        !UsedModuleNames, !FoundUnqual),
    collect_used_modules_in_promise_goals(Goals,
        !UsedModuleNames, !FoundUnqual).

:- pred collect_used_modules_in_promise_catch(catch_expr::in,
    set_tree234(module_name)::in, set_tree234(module_name)::out,
    bool::in, bool::out) is det.

collect_used_modules_in_promise_catch(CatchExpr,
        !UsedModuleNames, !FoundUnqual) :-
    CatchExpr = catch_expr(Pattern0, Goal),
    term.coerce(Pattern0, Pattern),
    collect_used_modules_in_term(Pattern, !UsedModuleNames, !FoundUnqual),
    collect_used_modules_in_promise_goal(Goal, !UsedModuleNames, !FoundUnqual).

    % Add all the module names in qualified sym_names in Term to
    % !UsedModuleNames, and set !FoundUnqual to true if any of the sym_names
    % in Term is unqualified.
    %
:- pred collect_used_modules_in_term(term::in,
    set_tree234(module_name)::in, set_tree234(module_name)::out,
    bool::in, bool::out) is det.

collect_used_modules_in_term(Term, !UsedModuleNames, !FoundUnqual) :-
    ( if try_parse_sym_name_and_args(Term, SymName, ArgTerms) then
        (
            SymName = qualified(ModuleName, _),
            set_tree234.insert(ModuleName, !UsedModuleNames)
        ;
            SymName = unqualified(_),
            !:FoundUnqual = yes
        ),
        collect_used_modules_in_terms(ArgTerms, !UsedModuleNames, !FoundUnqual)
    else
        true
    ).

:- pred collect_used_modules_in_terms(list(term)::in,
    set_tree234(module_name)::in, set_tree234(module_name)::out,
    bool::in, bool::out) is det.

collect_used_modules_in_terms([], !UsedModuleNames, !FoundUnqual).
collect_used_modules_in_terms([Term | Terms],
        !UsedModuleNames, !FoundUnqual) :-
    collect_used_modules_in_term(Term, !UsedModuleNames, !FoundUnqual),
    collect_used_modules_in_terms(Terms, !UsedModuleNames, !FoundUnqual).

%---------------------------------------------------------------------------%
:- end_module parse_tree.module_qual.collect_mq_info.
%---------------------------------------------------------------------------%
