%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2005-2012 The University of Melbourne.
% Copyright (C) 2014-2025 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: typecheck_error_undef.m.
%
% This file contains predicates to report references to undefined predicates,
% functions, data constructors and events.
%
%---------------------------------------------------------------------------%

:- module check_hlds.typecheck_error_undef.
:- interface.

:- import_module check_hlds.typecheck_error_util.
:- import_module check_hlds.typecheck_info.
:- import_module hlds.
:- import_module hlds.hlds_cons.
:- import_module hlds.hlds_data.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_pred.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.
:- import_module parse_tree.error_spec.
:- import_module parse_tree.prog_data.

:- import_module list.

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

:- func report_error_call_to_undef_pred(type_error_clause_context,
    prog_context, sym_name_pred_form_arity) = error_spec.

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

:- func report_error_undef_non_du_ctor(type_error_clause_context,
    type_error_goal_context, prog_context, cons_id) = error_spec.

:- type cons_error
    --->    other_lang_foreign_type_constructor(type_ctor, hlds_type_defn)
    ;       abstract_imported_type
    ;       invalid_field_update(sym_name, hlds_ctor_field_defn,
                tvarset, list(tvar))
    ;       new_on_non_existential_type(type_ctor).

:- func report_error_undef_du_ctor(type_error_clause_context,
    type_error_goal_context, prog_context, du_ctor, list(cons_error))
    = error_spec.

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

    % Report that there is no event with the given name.
    %
:- func report_error_undef_event(prog_context, string) = error_spec.

    % Report that there is an event with the given name, but not with
    % the given arity.
    %
:- func report_error_undef_event_arity(prog_context, string, list(mer_type),
    list(prog_var)) = error_spec.

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

:- func maybe_report_no_clauses(module_info, pred_id, pred_info)
    = list(error_spec).

:- func maybe_report_no_clauses_stub(module_info, pred_id, pred_info)
    = list(error_spec).

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

:- implementation.

:- import_module hlds.hlds_error_util.
:- import_module hlds.hlds_markers.
:- import_module hlds.pred_table.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.options.
:- import_module mdbcomp.builtin_modules.
:- import_module mdbcomp.prim_data.
:- import_module parse_tree.prog_util.

:- import_module bool.
:- import_module int.
:- import_module map.
:- import_module maybe.
:- import_module one_or_more.
:- import_module require.
:- import_module set.
:- import_module string.
:- import_module term.
:- import_module varset.

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% The implementation of report_error_undef_pred.
%

report_error_call_to_undef_pred(ClauseContext, Context, SymNameArity) = Spec :-
    SymNameArity = sym_name_pred_form_arity(SymName, _PredFormArity),
    ModuleInfo = ClauseContext ^ tecc_module_info,
    module_info_get_predicate_table(ModuleInfo, PredicateTable),
    PredMarkers = ClauseContext ^ tecc_pred_markers,
    IsFullyQualified = calls_are_fully_qualified(PredMarkers),
    predicate_table_lookup_pf_sym(PredicateTable, IsFullyQualified,
        pf_predicate, SymName, OtherIds),
    (
        OtherIds = [_ | _],
        predicate_table_get_pred_id_table(PredicateTable, PredIdTable),
        find_pred_arities(PredIdTable, OtherIds, PredFormArities),
        Spec = report_error_pred_wrong_arity(ClauseContext, Context,
            SymNameArity, PredFormArities)
    ;
        OtherIds = [],
        is_undef_pred_reference_special(ClauseContext, SymNameArity,
            UndefClass),
        (
            UndefClass = undef_special(SpecialComponents),
            InClauseForPieces = in_clause_for_pieces(ClauseContext),
            InClauseForComponent = always(InClauseForPieces),
            Msg = simple_msg(Context,
                [InClauseForComponent | SpecialComponents]),
            Spec = error_spec($pred, severity_error, phase_type_check, [Msg])
        ;
            UndefClass = undef_ordinary(MissingImportModules, AddendumPieces,
                MaySuggestDefiningModules),
            Spec = report_error_pred_wrong_full_name(ClauseContext, Context,
                PredicateTable, SymNameArity, MissingImportModules,
                AddendumPieces, MaySuggestDefiningModules)
        )
    ).

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

:- func report_error_pred_wrong_arity(type_error_clause_context, prog_context,
    sym_name_pred_form_arity, list(pred_form_arity)) = error_spec.

report_error_pred_wrong_arity(ClauseContext, Context, SymNameArity,
        AllPredFormArities) = Spec :-
    SymNameArity = sym_name_pred_form_arity(SymName, PredFormArity),
    PredFormArity = pred_form_arity(PredFormArityInt),
    AllPredFormArityInts =
        list.map(project_pred_form_arity_int, AllPredFormArities),
    MainPieces = in_clause_for_pieces(ClauseContext) ++
        [words("error:")] ++
        arity_error_to_pieces(pf_predicate, PredFormArityInt,
            AllPredFormArityInts) ++ [nl] ++
        [words("in call to"), p_or_f(pf_predicate)] ++
        color_as_subject([qual_sym_name(SymName), suffix(".")]) ++
        [nl],
    ( if
        % A call to process_options or to process_options_track in getopt_io
        % may appear in the source code either explicitly qualified,
        % or unqualified. If not explicitly qualified by the user,
        % it won't be qualified by the compiler either, due to
        % the arity mismatch.
        (
            SymName = unqualified(PredName),
            StdLibModuleName = "getopt_io"
        ;
            SymName = qualified(ModuleName, PredName),
            is_std_lib_module_name(ModuleName, StdLibModuleName),
            StdLibModuleName = "getopt_io"
        ),
        % We add SpecialPieces if these predicates are called
        % with (one of) their old arities.
        (
            PredName = "process_options",
            ( PredFormArityInt = 6 ; PredFormArityInt = 7 )
        ;
            PredName = "process_options_track",
            PredFormArityInt = 9
        )
    then
        SpecialPieces =
            [words("One possible reason for the error is that"),
            words("the predicate in the"), quote(StdLibModuleName),
            words("module that used to be named"), quote(PredName)] ++
            color_as_hint([words("has been renamed to"),
                quote(PredName ++ "_io"), suffix(".")]) ++
            [nl]
    else
        SpecialPieces = []
    ),
    Spec = spec($pred, severity_error, phase_type_check,
        Context, MainPieces ++ SpecialPieces).

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

:- type undef_class
    --->    undef_special(list(error_msg_component))
            % This is not a reference to an undefined predicate,
            % but a badly-executed attempt at using Mercury's "keywords".
    ;       undef_ordinary(
                % This is an ordinary reference to an undefined predicate.

                % The modules whose import may fix the reference.
                list(module_name),

                % An addendum to be printed after the usual messages.
                list(format_piece),

                % May the caller suggest modules which define the undefined
                % symbol? Set to "may_not_suggest_defining_modules"
                % if the addendum pieces have already done that job.
                may_suggest_defining_modules
            ).

:- type may_suggest_defining_modules
    --->    may_not_suggest_defining_modules
    ;       may_suggest_defining_modules.

:- pred is_undef_pred_reference_special(type_error_clause_context::in,
    sym_name_pred_form_arity::in, undef_class::out) is det.

is_undef_pred_reference_special(ClauseContext, SymNameArity, UndefClass) :-
    SymNameArity = sym_name_pred_form_arity(PredSymName, PredFormArity),
    PredFormArity = pred_form_arity(PredFormArityInt),
    ( if
        PredSymName = unqualified(PredName),
        is_undef_pred_a_syntax_error(PredName, PredFormArityInt,
            UndefSyntaxComponents)
    then
        UndefClass = undef_special(UndefSyntaxComponents)
    else
        (
            PredSymName = qualified(ModuleQualifier, _),
            maybe_report_missing_import_addendum(ClauseContext,
                ModuleQualifier,
                MissingImportAddendumPieces, MissingImportModules),
            MaySuggestDefiningModules = may_suggest_defining_modules
        ;
            PredSymName = unqualified(BaseName),
            ( if
                PredSymName = unqualified(BaseName),
                ( BaseName = "<"
                ; BaseName = "=<"
                ; BaseName = ">"
                ; BaseName = ">="
                )
            then
                MissingImportAddendumPieces =
                    construct_arith_import_pieces(nok_compare, BaseName),
                % There are missing module imports, but the call above
                % reports them; there is no need for our caller to do so again.
                MissingImportModules = [],
                MaySuggestDefiningModules = may_not_suggest_defining_modules
            else
                MissingImportAddendumPieces = [],
                MissingImportModules = [],
                MaySuggestDefiningModules = may_suggest_defining_modules
            )
        ),
        maybe_warn_about_getopt_changes(PredSymName, PredFormArityInt,
            GetoptPieces),
        AddendumPieces = MissingImportAddendumPieces ++ GetoptPieces,
        UndefClass = undef_ordinary(MissingImportModules, AddendumPieces,
            MaySuggestDefiningModules)
    ).

:- pred is_undef_pred_a_syntax_error(string::in, int::in,
    list(error_msg_component)::out) is semidet.

is_undef_pred_a_syntax_error(PredName, PredFormArityInt, Components) :-
    (
        PredName = "->",
        ( PredFormArityInt = 2 ; PredFormArityInt = 4 ),
        MainPieces = [words("error: this")] ++
            color_as_subject([quote("->")]) ++
            [words("has no matching")] ++
            color_as_incorrect([quote(";"), suffix(".")]) ++
            [nl],
        MainComponent = always(MainPieces),
        VerbosePieces =
            [words("Note: the"), quote("else"), words("part is not optional."),
            nl, words("Every if-then must have an"),
            quote("else"), suffix("."), nl],
        VerboseComponent = verbose_only(verbose_once, VerbosePieces),
        Components = [MainComponent, VerboseComponent]
    ;
        PredName = "else",
        ( PredFormArityInt = 2 ; PredFormArityInt = 4 ),
        Pieces = [words("error: this")] ++
            color_as_subject([quote("else")]) ++
            color_as_incorrect([words("has no matching")]) ++
            color_as_hint([quote("if")]) ++
            [words("or")] ++
            color_as_hint([quote("then"), suffix(".")]) ++
            [nl],
        Components = [always(Pieces)]
    ;
        PredName = "if",
        ( PredFormArityInt = 2 ; PredFormArityInt = 4 ),
        Pieces = [words("error: this")] ++
            color_as_subject([quote("if")]) ++
            [words("has no matching")] ++
            color_as_hint([quote("then")]) ++
            [words("or")] ++
            color_as_hint([quote("else"), suffix(".")]) ++
            [nl],
        Components = [always(Pieces)]
    ;
        PredName = "then",
        ( PredFormArityInt = 2 ; PredFormArityInt = 4 ),
        MainPieces = [words("error: this")] ++
            color_as_subject([quote("then")]) ++
            [words("has no matching")] ++
            color_as_hint([quote("if")]) ++
            [words("or")] ++
            color_as_hint([quote("else"), suffix(".")]) ++
            [nl],
        MainComponent = always(MainPieces),
        VerbosePieces =
            [words("Note: the"), quote("else"), words("part is not optional."),
            nl, words("Every if-then must have an"),
            quote("else"), suffix("."), nl],
        VerboseComponent = verbose_only(verbose_once, VerbosePieces),
        Components = [MainComponent, VerboseComponent]
    ;
        PredName = "apply",
        PredFormArityInt >= 1,
        Components = report_apply_instead_of_pred
    ;
        ( PredName = "semipure"
        ; PredName = "impure"
        ),
        PredFormArityInt = 1,
        Pieces = [words("error: this")] ++
            color_as_subject([quote(PredName), words("marker")]) ++
            [words("is")] ++
            color_as_incorrect([words("not before a predicate call.")]) ++
            [nl],
        Components = [always(Pieces)]
    ;
        PredName = "some",
        PredFormArityInt = 2,
        Pieces = [words("syntax error in existential quantification:")] ++
            [words("the")] ++
            color_as_subject([words("first argument of"), quote("some")]) ++
            color_as_incorrect([words("should be a list of variables.")]) ++
            [nl],
        Components = [always(Pieces)]
    ).

:- func report_apply_instead_of_pred = list(error_msg_component).

report_apply_instead_of_pred = Components :-
    MainPieces = [words("error: the language construct")] ++
        color_as_subject([quote("apply")]) ++
        color_as_incorrect([words("should be used as an expression,"),
            words("not as a goal.")]) ++
        [nl],
    MainComponent = always(MainPieces),
    VerbosePieces =
        [words("Perhaps you forgot to add"), quote(" = ..."), suffix("?)"), nl,
        words("If you are trying to invoke a higher-order predicate,"),
        words("use"), quote("call"), suffix(","),
            words("not"), quote("apply"), suffix("."), nl,
        words("If you are trying to curry a higher-order function,"),
        words("use a forwarding function:"), nl,
        words("e.g. instead of "), quote("NewFunc = apply(OldFunc, X)"),
        words("use"), quote("NewFunc = my_apply(OldFunc, X)"),
        words("where"), quote("my_apply"), words("is defined"),
        words("with the appropriate arity, e.g."),
        quote("my_apply(Func, X, Y) :- apply(Func, X, Y)."), nl],
    VerboseComponent = verbose_only(verbose_always, VerbosePieces),
    Components = [MainComponent, VerboseComponent].

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

:- pred maybe_warn_about_getopt_changes(sym_name::in, int::in,
    list(format_piece)::out) is det.

maybe_warn_about_getopt_changes(PredSymName, PredFormArityInt, GetoptPieces) :-
    ( if
        % A call to process_options_se or to process_options_track_se
        % in getopt or getopt_io may appear in the source code either
        % explicitly qualified, or unqualified. If not explicitly
        % qualified by the user, it won't be qualified by the compiler
        % either, due to the wrong name.
        (
            PredSymName = unqualified(PredName)
        ;
            PredSymName = qualified(ModuleName, PredName),
            is_std_lib_module_name(ModuleName, StdLibModuleName),
            ( StdLibModuleName = "getopt"
            ; StdLibModuleName = "getopt_io"
            )
        ),
        % We add SpecialPieces if these predicates are called
        % with (one of) their old arities. (If they are called
        % with any other arity, then the caller didn't work
        % with the old contents of the getopt modules either.)
        (
            PredName = "process_options_se",
            ( PredFormArityInt = 4
            ; PredFormArityInt = 5
            ; PredFormArityInt = 6
            ; PredFormArityInt = 7
            ),
            NewPredName = "process_options"
        ;
            PredName = "process_options_track_se",
            ( PredFormArityInt = 7
            ; PredFormArityInt = 9
            ),
            NewPredName = "process_options_track"
        )
    then
        GetoptPieces =
            [words("One possible reason for the error is that"),
            words("the predicate"), quote(PredName),
            words("in the Mercury standard library has been")] ++
            color_as_hint([words("renamed to"),
                quote(NewPredName), suffix(".")]) ++
            [nl]
    else
        GetoptPieces = []
    ).

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

:- func report_error_pred_wrong_full_name(type_error_clause_context,
    prog_context, predicate_table, sym_name_pred_form_arity, list(module_name),
    list(format_piece), may_suggest_defining_modules) = error_spec.

report_error_pred_wrong_full_name(ClauseContext, Context, PredicateTable,
        SymNamePredFormArity, MissingImportModules, AddendumPieces,
        MaySuggestDefiningModules) = Spec :-
    InClauseForPieces = in_clause_for_pieces(ClauseContext),
    InClauseForComponent = always(InClauseForPieces),
    SymNamePredFormArity = sym_name_pred_form_arity(SymName, PredFormArity),
    PredFormArity = pred_form_arity(Arity),
    SNA = sym_name_arity(SymName, Arity),
    MainPieces = [words("error: call to")] ++
        color_as_incorrect([words("undefined")]) ++
        [words("predicate")] ++
        color_as_subject([qual_sym_name_arity(SNA), suffix(".")]) ++
        [nl],
    UndefMsg = simple_msg(Context,
        [InClauseForComponent, always(MainPieces ++ AddendumPieces)]),

    predicate_table_lookup_pf_sym(PredicateTable, may_be_partially_qualified,
        pf_function, SymName, FuncOtherIds),
    (
        FuncOtherIds = [_ | _],
        KindMsg = report_error_func_instead_of_pred(Context),
        KindMsgs = [KindMsg]
    ;
        FuncOtherIds = [],
        KindMsgs = []
    ),
    PossibleModuleQuals =
        find_possible_pf_missing_module_qualifiers(PredicateTable,
            pf_predicate, SymName),
    set.list_to_set(PossibleModuleQuals, PossibleModuleQualsSet0),
    set.delete_list(MissingImportModules,
        PossibleModuleQualsSet0, PossibleModuleQualsSet),
    (
        MaySuggestDefiningModules = may_not_suggest_defining_modules,
        KindQualMsgs = KindMsgs
    ;
        MaySuggestDefiningModules = may_suggest_defining_modules,
        QualMsgs = report_any_missing_module_qualifiers(ClauseContext,
            Context, "predicate", PossibleModuleQualsSet),
        KindQualMsgs = KindMsgs ++ QualMsgs
    ),
    ( if
        AddendumPieces = [],
        KindQualMsgs = []
    then
        KindQualMsgs = [],
        % It seems that regardless of missing qualifications or equal signs,
        % the reference is to the wrong name. See if we can mention some
        % similar names that could be the one they intended.
        get_known_pred_info_names(PredicateTable, pf_predicate,
            KnownPredNames),
        BaseName = unqualify_name(SymName),
        maybe_construct_did_you_mean_pieces(BaseName, KnownPredNames,
            DidYouMeanPieces),
        (
            DidYouMeanPieces = [],
            Msgs = [UndefMsg]
        ;
            DidYouMeanPieces = [_ | _],
            DidyouMeanMsg = msg(Context, DidYouMeanPieces),
            Msgs = [UndefMsg, DidyouMeanMsg]
        )
    else
        % The AddendumPieces part of UndefMsg and/or KindQualMsgs
        % offer hints about the error that allow for the base name being right.
        % Print just those.
        % XXX Should we print SuggestedNamesMsg as well even in this case?
        Msgs = [UndefMsg] ++ KindQualMsgs
    ),
    Spec = error_spec($pred, severity_error, phase_type_check, Msgs).

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

:- func report_error_func_instead_of_pred(prog_context) = error_msg.

report_error_func_instead_of_pred(Context) = Msg :-
    Pieces = [words("(There is a *function* with that name, however."), nl] ++
        color_as_hint([words("Perhaps you forgot to add"),
            quote(" = ..."), suffix("?)")]) ++
        [nl],
    Msg = msg(Context, Pieces).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% The implementation of report_error_undef_non_du_ctor.
%

report_error_undef_non_du_ctor(ClauseContext, GoalContext, Context, ConsId)
        = Spec :-
    InClauseForPieces = in_clause_for_pieces(ClauseContext),
    GoalContextPieces = goal_context_to_pieces(ClauseContext, GoalContext),
    InitComp = always(InClauseForPieces ++ GoalContextPieces),
    ConsIdPiece = qual_cons_id_and_maybe_arity(ConsId),
    UndefPieces = [words("error:")] ++
        color_as_incorrect([words("undefined")]) ++
        [words("symbol")] ++
        color_as_subject([ConsIdPiece, suffix(".")]) ++
        [nl],
    UndefComp = always(UndefPieces),
    Msg = simple_msg(Context, [InitComp, UndefComp]),
    Spec = error_spec($pred, severity_error, phase_type_check, [Msg]).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% The implementation of report_error_undef_du_ctor.
%

report_error_undef_du_ctor(ClauseContext, GoalContext, Context, DuCtor,
        ConsErrors) = Spec :-
    InClauseForPieces = in_clause_for_pieces(ClauseContext),
    GoalContextPieces = goal_context_to_pieces(ClauseContext, GoalContext),
    ContextComp = always(InClauseForPieces ++ GoalContextPieces),
    % Check for some special cases, so that we can give clearer error messages.
    DuCtor = du_ctor(SymName, Arity, _),
    ( if
        SymName = unqualified(Name),
        ( if language_builtin_functor(Name, Arity, UsedAsWhat) then
            language_builtin_functor_components(Name, Arity, UsedAsWhat,
                FunctorComps)
        else if syntax_functor_components(Name, Arity, SyntaxComps) then
            FunctorComps = SyntaxComps
        else
            fail
        )
    then
        Spec = error_spec($pred, severity_error, phase_type_check,
            [simple_msg(Context, [ContextComp | FunctorComps])])
    else
        report_error_undef_du_ctor_std(ClauseContext, Context, ContextComp,
            DuCtor, ConsErrors, Spec)
    ).

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

    % language_builtin_functor(Name, Arity) is true iff Name/Arity is the name
    % of a builtin language construct that should be used as a goal,
    % not as an expression.
    %
:- pred language_builtin_functor(string::in, arity::in, string::out)
    is semidet.

language_builtin_functor("=", 2, "part of a goal").
language_builtin_functor("\\=", 2, "part of a goal").
language_builtin_functor(",", 2, "a connective between goals").
language_builtin_functor(";", 2, "a connective between goals").
language_builtin_functor("\\+", 1, "a prefix before a goal").
language_builtin_functor("not", 1, "a prefix before a goal").
language_builtin_functor("<=>", 2, "a connective between goals").
language_builtin_functor("=>", 2, "a connective between goals").
language_builtin_functor("<=", 2, "a connective between goals").
language_builtin_functor("call", _, "a prefix before a goal").
language_builtin_functor("impure", 1, "a prefix before a goal").
language_builtin_functor("semipure", 1, "a prefix before a goal").
language_builtin_functor("all", 2, "part of a goal").
language_builtin_functor("some", 2, "part of a goal").

:- pred language_builtin_functor_components(string::in, arity::in,
    string::in, list(error_msg_component)::out) is det.

language_builtin_functor_components(Name, Arity, UsedAsWhat, Components) :-
    NameArity = name_arity(Name, Arity),
    MainPieces = [words("error: the language construct")] ++
        color_as_subject([name_arity(NameArity)]) ++
        [words("should be used as")] ++
        color_as_correct([words(UsedAsWhat), suffix(",")]) ++
        color_as_incorrect([words("not as an expression.")]) ++
        [nl],
    ( if Name = "call" then
        VerboseCallPieces =
            [words("If you are trying to invoke a higher-order function,"),
            words("you should use")] ++
            color_as_correct([quote("apply"), suffix(",")]) ++
            [words("not")] ++
            color_as_incorrect([quote("call"), suffix(".")]) ++
            [nl,
            words("If you are trying to curry a higher-order function,"),
            words("see the ""Creating higher-order terms"" section"),
            words("of the Mercury Language Reference Manual."), nl,
            words("If you really are trying to use"),
            quote("call"), words("as an expression"),
            words("and not as an application of the language builtin call/N,"),
            words("make sure that you have the arity correct,"),
            words("and that the functor"), quote("call"),
            words("is actually defined."),
            words("(If it is defined in a separate module,"),
            words("check that the module is correctly imported.)"), nl]
    else
        VerboseCallPieces = []
    ),
    Components = [always(MainPieces),
        verbose_only(verbose_once, VerboseCallPieces)].

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

:- pred syntax_functor_components(string::in, arity::in,
    list(error_msg_component)::out) is semidet.

syntax_functor_components(FunctorName, Arity, Components) :-
    (
        Arity = 1,
        FunctorName = "!",
        Pieces = [words("error:")] ++
            color_as_incorrect([words("invalid use of the"), quote("!"),
                words("state variable operator.")]) ++
            [nl] ++
            color_as_hint([words("You probably meant to type"),
                quote("!."), words("or"), quote("!:"),
                words("instead of just"), quote("!"), suffix(".")]) ++
            [nl,
            words("If you are trying to do a field update, then")] ++
            color_as_hint([words("you probably typed"), quote("="),
                words("where you meant to type"), quote(":="), suffix(".")]) ++
            [nl],
        Components = [always(Pieces)]
    ;
        Arity = 2,
        (
            (
                FunctorName = "if",
                Pieces = [words("error: this")] ++
                    color_as_subject([quote("if")]) ++
                    [words("has no matching")] ++
                    color_as_hint([quote("then")]) ++
                    [words("or")] ++
                    color_as_hint([quote("else"), suffix(".")]) ++
                    [nl]
            ;
                FunctorName = "else",
                Pieces = [words("error: this")] ++
                    color_as_subject([quote("else")]) ++
                    color_as_incorrect([words("has no matching")]) ++
                    color_as_hint([quote("if")]) ++
                    [words("or")] ++
                    color_as_hint([quote("then"), suffix(".")]) ++
                    [nl]
            ;
                FunctorName = ":-",
                Pieces = [words("syntax error in lambda expression"),
                     prefix("("), quote(":-"), suffix(")."), nl]
            ;
                FunctorName = "-->",
                Pieces = [words("syntax error in DCG lambda expression"),
                    prefix("("), quote("-->"), suffix(")."), nl]
            ;
                FunctorName = ".",
                NameArity = name_arity("[|]", 2),
                Pieces = [words("error: the list constructor is")] ++
                    color_as_correct([name_arity(NameArity), suffix(",")]) ++
                    [words("not")] ++
                    color_as_incorrect([quote("./2"), suffix(".")]) ++
                    [nl]
            ),
            Components = [always(Pieces)]
        ;
            (
                FunctorName = "then",
                MainPieces = [words("error: this")] ++
                    color_as_subject([quote("then")]) ++
                    [words("has no matching")] ++
                    color_as_hint([quote("if")]) ++
                    [words("or")] ++
                    color_as_hint([quote("else"), suffix(".")]) ++
                    [nl],
                VerbosePieces =
                    [words("Note: the"), quote("else"),
                    words("part is not optional."),
                    nl, words("Every if-then must have an"),
                    quote("else"), suffix("."), nl]
            ;
                FunctorName = "->",
                MainPieces = [words("error: this")] ++
                    color_as_subject([quote("->")]) ++
                    [words("has no matching")] ++
                    color_as_incorrect([quote(";"), suffix(".")]) ++
                    [nl],
                VerbosePieces =
                    [words("Note: the"), quote("else"),
                    words("part is not optional."), nl,
                    words("Every if-then must have an"),
                    quote("else"), suffix("."), nl]
            ;
                FunctorName = "^",
                MainPieces = [words("error:")] ++
                    color_as_incorrect([words("invalid use of the"),
                        words("field selection operator"),
                        prefix("("), quote("^"), suffix(").")]) ++
                    [nl],
                VerbosePieces =
                    color_as_hint([words("This is probably"),
                        words("some kind of syntax error."),
                        words("The field name must be an atom,"),
                        words("not a variable or other term.")]) ++
                    [nl]
            ;
                FunctorName = ":=",
                MainPieces = [words("error:")] ++
                    color_as_incorrect([words("invalid use of the"),
                        words("field update operator"),
                        prefix("("), quote(":="), suffix(").")]) ++
                    [nl],
                VerbosePieces =
                    color_as_hint([words("This is probably"),
                        words("some kind of syntax error.")]) ++
                    [nl]
            ),
            Components =
                [always(MainPieces),
                verbose_only(verbose_always, VerbosePieces)]
        )
    ).

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

:- pred report_error_undef_du_ctor_std(type_error_clause_context::in,
    prog_context::in, error_msg_component::in, du_ctor::in,
    list(cons_error)::in, error_spec::out) is det.

report_error_undef_du_ctor_std(ClauseContext, Context, ContextComp, DuCtor,
        ConsErrors, Spec) :-
    ConsMsgLists = list.map(report_cons_error(Context), ConsErrors),
    list.condense(ConsMsgLists, ConsMsgs),

    ModuleInfo = ClauseContext ^ tecc_module_info,
    module_info_get_cons_table(ModuleInfo, ConsTable),
    module_info_get_predicate_table(ModuleInfo, PredicateTable),

    DuCtor = du_ctor(SymName, Arity, _),
    return_cons_arities(ConsTable, SymName, ConsArities),
    predicate_table_lookup_sym(PredicateTable, may_be_partially_qualified,
        SymName, PredIds),
    return_pred_func_arities(ModuleInfo, PredIds,
        [], PredArities, [], FuncArities),
    % A program can refer to functions both
    %
    % - by calling them, which requires their full list of arguments, and
    % - by constructing closures from them, which requires any initial
    %   subsequence of their full argument list.
    %
    % For a function with N arguments, the first requires N args in the
    % call, while the second is ok with any number between 0 and N.
    %
    % This is why we report FuncArities twice, with each message being
    % specific to one of the situations above.
    ConsFuncArities0 = ConsArities ++ FuncArities,
    list.sort_and_remove_dups(ConsFuncArities0, ConsFuncArities),
    list.delete_all(ConsFuncArities, Arity, OtherConsFuncArities),
    (
        OtherConsFuncArities = [_ | _],
        ConsFuncPieces = wrong_arity_constructor_to_pieces(SymName,
            Arity, OtherConsFuncArities),
        ConsFuncComps = [always(ConsFuncPieces)],
        % The code that constructs QualMsgs below uses wording that
        % can be misleading in the presence of arity mismatches.
        SuggestionMsgs = []
    ;
        OtherConsFuncArities = [],
        ConsIdPiece = qual_cons_id_and_maybe_arity(du_data_ctor(DuCtor)),
        UndefSymbolPieces = [words("error:")] ++
            color_as_incorrect([words("undefined")]) ++
            [words("symbol")] ++
            color_as_subject([ConsIdPiece, suffix(".")]) ++
            [nl],
        ( if is_du_ctor_numeric_op(DuCtor, DuCtorFunctor, OpKind) then
            AddendumPieces =
                construct_arith_import_pieces(OpKind, DuCtorFunctor),
            SuggestionMsgs = []
        else
            maybe_construct_missing_qualifier_msg(ClauseContext, Context,
                DuCtor, AddendumPieces, QualMsgs),
            ( if
                ConsMsgs = [],
                QualMsgs = []
            then
                maybe_construct_did_you_mean_msg(ModuleInfo, Context, DuCtor,
                    SuggestionMsgs)
            else
                SuggestionMsgs = QualMsgs
            )
        ),
        ConsFuncComps = [always(UndefSymbolPieces ++ AddendumPieces)]
    ),
    PredMarkers = ClauseContext ^ tecc_pred_markers,
    ( if
        marker_is_present(PredMarkers, marker_named_class_instance_method)
    then
        % We are processing a clause that the compiler generated itself
        % for an instance declaration such as "func(func_a/3) is one_str"
        % in tests/invalid/no_method.m. If we did not disable the
        % execution of the else-part in such contexts, we would get
        % misleading error messages such as
        %
        % no_method.m:035:   If you are trying to construct a closure
        % no_method.m:035:   containing `one_str', you cannot do so with
        % no_method.m:035:   3 arguments: the only function with this name
        % no_method.m:035:   has arity 2.
        %
        % I (zs) found that out the hard way :-(
        PredFuncComps = []
    else
        report_closure_construction_errors(SymName, Arity,
            PredArities, FuncArities, PredFuncComps)
    ),
    FirstMsg = simple_msg(Context,
        [ContextComp | ConsFuncComps] ++ PredFuncComps),
    Spec = error_spec($pred, severity_error, phase_type_check,
        [FirstMsg | ConsMsgs] ++ SuggestionMsgs).

:- pred return_pred_func_arities(module_info::in, list(pred_id)::in,
    list(int)::in, list(int)::out,
    list(int)::in, list(int)::out) is det.

return_pred_func_arities(_, [], !PredArities, !FuncArities).
return_pred_func_arities(ModuleInfo, [PredId | PredIds],
        !PredArities, !FuncArities) :-
    module_info_pred_info(ModuleInfo, PredId, PredInfo),
    pred_info_get_is_pred_or_func(PredInfo, PredOrFunc),
    pred_info_get_orig_arity(PredInfo, PredFormArity),
    user_arity_pred_form_arity(PredOrFunc,
        user_arity(UserArityInt), PredFormArity),
    (
        PredOrFunc = pf_predicate,
        !:PredArities = [UserArityInt | !.PredArities]
    ;
        PredOrFunc = pf_function,
        !:FuncArities = [UserArityInt | !.FuncArities]
    ),
    return_pred_func_arities(ModuleInfo, PredIds, !PredArities, !FuncArities).

:- func wrong_arity_constructor_to_pieces(sym_name, arity, list(int))
    = list(format_piece).

wrong_arity_constructor_to_pieces(Name, Arity, ActualArities) = Pieces :-
    % Constructors' arities should be treated the same way as
    % predicates' arities, in that neither has a distinguished "return value"
    % argument.
    NumArgsPieces = arity_error_to_pieces(pf_predicate, Arity, ActualArities),
    Pieces = [words("error:")] ++ NumArgsPieces ++
        [words("in use of constructor")] ++
        color_as_subject([qual_sym_name(Name), suffix(".")]) ++
        [nl].

:- pred accumulate_matching_cons_module_names(sym_name::in, hlds_cons_defn::in,
    list(module_name)::in, list(module_name)::out) is det.

accumulate_matching_cons_module_names(FunctorSymName, ConsDefn,
        !ModuleNames) :-
    type_ctor(TypeCtorSymName, _) = ConsDefn ^ cons_type_ctor,
    (
        TypeCtorSymName = unqualified(_)
        % There can be no problem with use_module replacing import_module
        % if the hlds_cons_defn is for a builtin data constructor.
    ;
        TypeCtorSymName = qualified(TypeCtorModuleName, _),
        FunctorName = unqualify_name(FunctorSymName),
        FullSymName = qualified(TypeCtorModuleName, FunctorName),
        ( if partial_sym_name_matches_full(FunctorSymName, FullSymName) then
            !:ModuleNames = [TypeCtorModuleName | !.ModuleNames]
        else
            true
        )
    ).

:- pred get_all_field_names(module_info::in, set(string)::out) is det.

get_all_field_names(ModuleInfo, FieldNameSet) :-
    module_info_get_ctor_field_table(ModuleInfo, CtorFieldTable),
    map.foldl(accumulate_field_names, CtorFieldTable, [], FieldNames),
    set.list_to_set(FieldNames, FieldNameSet).

:- pred accumulate_field_names(sym_name::in,
    one_or_more(hlds_ctor_field_defn)::in,
    list(string)::in, list(string)::out) is det.

accumulate_field_names(FieldSymName, _OoMFieldDefns, !FieldNames) :-
    !:FieldNames = [unqualify_name(FieldSymName) | !.FieldNames].

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

:- func report_cons_error(prog_context, cons_error) = list(error_msg).

report_cons_error(Context, ConsError) = Msgs :-
    (
        ConsError = other_lang_foreign_type_constructor(TypeCtor, _),
        %   Pieces = [words("There are"),
        %       pragma_decl("foreign_type"), words("declarations"),
        %       words("for type"), qual_type_ctor(TypeCtor), suffix(","),
        %       words("so it is treated as an abstract type"),
        %       words("in all predicates and functions"),
        %       words("which are not implemented"),
        %       words("for those foreign types."), nl],
        %
        % XXX Until 2024 may 20, we used the above wording for this message,
        % even though it was quite confused, and also quite confusing.
        % I (zs) *think* that this replacement text is a faithful description
        % of the problem it attempted to describe, but I don't know for sure.
        %
        % I also don't like the fact that
        %
        % - starting with a Mercury type t being used in predicate p
        %   written in Mercury,
        %
        % - adding e.g. a Java definition for type t and a Java foreign_proc
        %   for predicate p
        %
        % - would make references to the function symbols of the Mercury
        %   definition of type t suddently invalid in the Mercury code of p.
        %
        % The presence of a definition of a type in foreign language L
        % should affect the validity of a piece of Mercury code using that
        % type *only* when the current grade targets L.
        Pieces = [words("There are"),
            pragma_decl("foreign_type"), words("declarations"),
            words("for type"), qual_type_ctor(TypeCtor),
            words("for at least one target language other than the one"),
            words("targeted by the current grade."),
            words("Due to a limitation of the Mercury implementation,"),
            words("this fact requires the compiler to treat this type"),
            words("as an abstract type in all Mercury code, which")] ++
            color_as_incorrect([words("disallows references to the"),
                words("function symbols of the type.")]) ++
            [nl],
        Msgs = [msg(Context, Pieces)]
    ;
        ConsError = abstract_imported_type,
        % For `abstract_imported_type' errors, the "undefined symbol" error
        % written by `report_error_undef_cons' is sufficient, so we do not
        % print an additional error message here.
        Msgs = []
    ;
        ConsError = invalid_field_update(FieldName, FieldDefn, TVarSet, TVars),
        FieldDefn = hlds_ctor_field_defn(DefnContext, _, _, DuCtor, _),
        (
            TVars = [],
            unexpected($pred, "no type variables")
        ;
            TVars = [TVar],
            VarVars  = "variable",
            TVarPiece = var_to_quote_piece(TVarSet, TVar),
            TVarsPieces = color_as_subject([TVarPiece]),
            OccurOccurs = "occurs"
        ;
            TVars = [_, _ | _],
            VarVars  = "variables",
            TVarPieces = list.map(var_to_quote_piece(TVarSet), TVars),
            TVarsPieces = piece_list_to_color_pieces(color_subject, "and", [],
                TVarPieces),
            OccurOccurs = "occur"
        ),
        ConsId = du_data_ctor(DuCtor),
        Pieces = [words("Field")] ++
            color_as_subject([unqual_sym_name(FieldName)]) ++
            color_as_incorrect([words("cannot be updated")]) ++
            [words("because the existentially quantified type"),
            words(VarVars)] ++
            TVarsPieces ++
            [words(OccurOccurs), words("not just in the type of this field,"),
            words("but also in the types of some of the other fields of"),
            qual_cons_id_and_maybe_arity(ConsId), suffix("."), nl],
        Msgs = [msg(DefnContext, Pieces)]
    ;
        ConsError = new_on_non_existential_type(TypeCtor),
        Pieces = [words("Invalid use of the")] ++
            % The space after "new" is deliberate.
            color_as_subject([quote("new "), words("prefix")]) ++
            [words("on a constructor of type"), qual_type_ctor(TypeCtor),
            suffix(","), words("which is")] ++
            color_as_incorrect([words("not existentially typed.")]) ++
            [nl],
        Msgs = [msg(Context, Pieces)]
    ).

:- pred report_closure_construction_errors(sym_name::in, int::in,
    list(int)::in, list(int)::in, list(error_msg_component)::out) is det.

report_closure_construction_errors(SymName, ConsIdArity,
        PredArities, FuncArities, PredFuncComps) :-
    list.sort_and_remove_dups(PredArities, SortedPredArities),
    list.sort_and_remove_dups(FuncArities, SortedFuncArities),
    report_closure_arities(pf_predicate, SortedPredArities, PredArityPieces),
    report_closure_arities(pf_function,  SortedFuncArities, FuncArityPieces),
    IfYouAreTryingPieces = [words("If you are trying to construct"),
        words("a closure containing")] ++
        color_as_subject([qual_sym_name(SymName), suffix(",")]) ++
        [words("you cannot do so with")] ++
        color_as_incorrect([int_fixed(ConsIdArity)]) ++
        [words("arguments:")],
    (
        PredArityPieces = [],
        FuncArityPieces = [],
        PredFuncComps = []
    ;
        PredArityPieces = [],
        FuncArityPieces = [_ | _],
        PredFuncPieces = IfYouAreTryingPieces ++ FuncArityPieces ++
            [suffix("."), nl],
        PredFuncComps = [always(PredFuncPieces)]
    ;
        PredArityPieces = [_ | _],
        FuncArityPieces = [],
        PredFuncPieces = IfYouAreTryingPieces ++ PredArityPieces ++
            [suffix("."), nl],
        PredFuncComps = [always(PredFuncPieces)]
    ;
        PredArityPieces = [_ | _],
        FuncArityPieces = [_ | _],
        PredFuncPieces = IfYouAreTryingPieces ++ PredArityPieces ++
            [suffix(","), words("and")] ++ FuncArityPieces ++
            [suffix("."), nl],
        PredFuncComps = [always(PredFuncPieces)]
    ).

:- pred report_closure_arities(pred_or_func::in, list(int)::in,
    list(format_piece)::out) is det.

report_closure_arities(PredOrFunc, Arities, Pieces) :-
    (
        Arities = [],
        Pieces = []
    ;
        Arities = [HeadArity | TailArities],
        (
            TailArities = [],
            Pieces = [words("the only"), p_or_f(PredOrFunc),
                words("with this name has arity")] ++
                color_as_correct([int_fixed(HeadArity)])
        ;
            TailArities = [_ | _],
            ArityPieces = list.map((func(N) = int_fixed(N)), Arities),
            AritiesPieces = piece_list_to_color_pieces(color_correct,
                "and", [], ArityPieces),
            Pieces = [words("the"), p_or_f(PredOrFunc), suffix("s"),
                words("with this name have arities")] ++ AritiesPieces
        )
    ).

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

:- pred maybe_construct_missing_qualifier_msg(type_error_clause_context::in,
    prog_context::in, du_ctor::in,
    list(format_piece)::out, list(error_msg)::out) is det.

maybe_construct_missing_qualifier_msg(ClauseContext, Context, DuCtor,
        AddendumPieces, QualMsgs) :-
    ModuleInfo = ClauseContext ^ tecc_module_info,
    module_info_get_cons_table(ModuleInfo, ConsTable),
    module_info_get_predicate_table(ModuleInfo, PredicateTable),

    DuCtor = du_ctor(SymName, Arity, _),
    ( if
        SymName = qualified(ModQual, _)
    then
        maybe_report_missing_import_addendum(ClauseContext, ModQual,
            AddendumPieces, MissingImportModules)
    else if
        SymName = unqualified("[|]"),
        Arity = 2
    then
        maybe_report_missing_import_addendum(ClauseContext,
            unqualified("list"), AddendumPieces, MissingImportModules)
    else if
        SymName = unqualified("coerce")
    then
        AddendumPieces = [words("(The builtin")] ++
            color_as_subject([words("coerce")]) ++
            [words("operator expects")] ++
            color_as_correct([words("one")]) ++
            [words("argument, not")] ++
            color_as_incorrect([int_name(Arity), suffix(".")]) ++
            [suffix(")"), nl],
        MissingImportModules = []
    else
        AddendumPieces = [],
        MissingImportModules = []
    ),
    BaseName = unqualify_name(SymName),
    return_cons_defns_with_given_name(ConsTable, BaseName, ConsDefns),
    list.foldl(accumulate_matching_cons_module_names(SymName),
        ConsDefns, [], ConsModuleNames),
    PredModuleNames =
        find_possible_pf_missing_module_qualifiers(PredicateTable,
            pf_predicate, SymName),
    FuncModuleNames =
        find_possible_pf_missing_module_qualifiers(PredicateTable,
            pf_function, SymName),
    ModuleNames = ConsModuleNames ++ PredModuleNames ++ FuncModuleNames,
    set.list_to_set(ModuleNames, ModuleNamesSet0),
    set.delete_list(MissingImportModules, ModuleNamesSet0, ModuleNamesSet),
    QualMsgs = report_any_missing_module_qualifiers(ClauseContext,
        Context, "symbol", ModuleNamesSet).

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

:- type numeric_op_kind
    --->    nok_arith_int_only
    ;       nok_arith_int_or_float
    ;       nok_bitwise
    ;       nok_compare.

:- pred is_du_ctor_numeric_op(du_ctor::in, string::out, numeric_op_kind::out)
    is semidet.

is_du_ctor_numeric_op(DuCtor, DuCtorFunctor, OpKind) :-
    DuCtor = du_ctor(unqualified(DuCtorFunctor), Arity, _),
    (
        Arity = 1,
        ( DuCtorFunctor = "+",      OpKind = nok_arith_int_or_float
        ; DuCtorFunctor = "-",      OpKind = nok_arith_int_or_float
        ; DuCtorFunctor = "\\",     OpKind = nok_bitwise
        )
    ;
        Arity = 2,
        ( DuCtorFunctor = "+",      OpKind = nok_arith_int_or_float
        ; DuCtorFunctor = "-",      OpKind = nok_arith_int_or_float
        ; DuCtorFunctor = "*",      OpKind = nok_arith_int_or_float
        ; DuCtorFunctor = "/",      OpKind = nok_arith_int_or_float
        ; DuCtorFunctor = "//",     OpKind = nok_arith_int_only
        ; DuCtorFunctor = "div",    OpKind = nok_arith_int_only
        ; DuCtorFunctor = "mod",    OpKind = nok_arith_int_only
        ; DuCtorFunctor = "rem",    OpKind = nok_arith_int_only
        % XXX Note that <<u and >>u are not supported by integer.m.
        % We could either fix this error message, or integer.m.
        % I (zs) prefer the latter.
        ; DuCtorFunctor = "<<",     OpKind = nok_bitwise
        ; DuCtorFunctor = "<<u",    OpKind = nok_bitwise
        ; DuCtorFunctor = ">>",     OpKind = nok_bitwise
        ; DuCtorFunctor = ">>u",    OpKind = nok_bitwise
        ; DuCtorFunctor = "/\\",    OpKind = nok_bitwise
        ; DuCtorFunctor = "\\/",    OpKind = nok_bitwise
        ; DuCtorFunctor = "xor",    OpKind = nok_bitwise
        )
    ).

:- func construct_arith_import_pieces(numeric_op_kind, string)
    = list(format_piece).

construct_arith_import_pieces(OpKind, OpStr) = Pieces :-
    (
        ( OpKind = nok_arith_int_only
        ; OpKind = nok_arith_int_or_float
        ),
        AOpKindStr = "an arithmetic operation",
        (
            OpKind = nok_arith_int_only,
            AndFloatPeriodPieces = [suffix(".")]
        ;
            OpKind = nok_arith_int_or_float,
            AndFloatPeriodPieces = [words("and")] ++
                color_as_subject([quote("float"), suffix(".")])
        ),
        GenComparePieces = []
    ;
        OpKind = nok_bitwise,
        AOpKindStr = "a bit manipulation operation",
        AndFloatPeriodPieces = [suffix(".")],
        GenComparePieces = []
    ;
        OpKind = nok_compare,
        AOpKindStr = "a numeric comparison operation",
        AndFloatPeriodPieces = [words("and")] ++
            color_as_subject([quote("float"), suffix(".")]),
        GenOpStr = "@" ++ OpStr,
        GenComparePieces = [words("If the values you are trying to compare"),
            words("are not numbers, then what you probably want is"),
            words("the builtin")] ++ color_as_hint([quote(GenOpStr)]) ++
            [words("operation."), nl]
    ),
    Pieces =
        [words("In Mercury, if you want to invoke"),
        words(AOpKindStr), words("such as")] ++
        color_as_subject([quote(OpStr), suffix(",")]) ++
        [words("then you must import a module that exports that operation."),
        words("The modules in the Mercury standard library that do so"),
        words("include")] ++ color_as_subject([quote("int"), suffix(",")]) ++
        color_as_subject([quote("uint"), suffix(",")]) ++
        [words("their sized versions (such as")] ++
        color_as_subject([quote("int8")]) ++ [words("and")] ++
        color_as_subject([quote("uint16")]) ++[suffix("),"),
        words("as well as")] ++ color_as_subject([quote("integer")]) ++
        AndFloatPeriodPieces ++
        [words("However,")] ++
        color_as_hint([words("none of those modules have"),
            decl("import_module"), words("declarations.")]) ++ [nl] ++
        GenComparePieces.

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

:- pred maybe_construct_did_you_mean_msg(module_info::in, prog_context::in,
    du_ctor::in, list(error_msg)::out) is det.

maybe_construct_did_you_mean_msg(ModuleInfo, Context, DuCtor,
        DidYouMeanMsgs) :-
    % It seems that the reference is to the wrong name.
    % See if we can mention some similar names that could be
    % the one they intended.
    module_info_get_cons_table(ModuleInfo, ConsTable),
    cons_table_names(ConsTable, ConsTableNameSet),
    module_info_get_predicate_table(ModuleInfo, PredicateTable),
    get_known_pred_info_names(PredicateTable, pf_function,
        KnownFuncNames0),
    set.sorted_list_to_set(KnownFuncNames0, KnownFuncNamesSet0),
    set.union(ConsTableNameSet,
        KnownFuncNamesSet0, KnownFuncNamesSet1),
    % While you are not allowed to curry (and thus reduce the
    % apparent arity) of an automatically-generated field access
    % function, you *are* allowed to curry a user-defined function
    % that has the same name. However, using "abcd :=" as a
    % suggested replacement for "abcdef" is probably a bad idea,
    % In fact, suggesting replacing anything with " :=" is a bad idea.
    DuCtor = du_ctor(SymName, Arity, _),
    BaseName = unqualify_name(SymName),
    ( if
        Arity = 1
    then
        get_all_field_names(ModuleInfo, FieldNames),
        set.union(FieldNames, KnownFuncNamesSet1, KnownFuncNamesSet)
    else if
        Arity = 2,
        % Allow suggestions that add back a missing space
        % before the ":=".
        string.suffix(BaseName, ":=")
    then
        get_all_field_names(ModuleInfo, FieldNames0),
        FieldNames = set.map(string.add_suffix(" :="), FieldNames0),
        set.union(FieldNames, KnownFuncNamesSet1, KnownFuncNamesSet)
    else
        KnownFuncNamesSet = KnownFuncNamesSet1
    ),
    set.to_sorted_list(KnownFuncNamesSet, KnownFuncNames),
    maybe_construct_did_you_mean_pieces(BaseName, KnownFuncNames,
        DidYouMeanPieces),
    (
        DidYouMeanPieces = [],
        DidYouMeanMsgs = []
    ;
        DidYouMeanPieces = [_ | _],
        DidyouMeanMsg = msg(Context, DidYouMeanPieces),
        DidYouMeanMsgs = [DidyouMeanMsg]
    ).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% The implementation of report_error_undef_event.
%

report_error_undef_event(Context, EventName) = Spec :-
    Pieces = [words("Error: there is")] ++
        color_as_incorrect([words("no event named"),
            quote(EventName), suffix(".")]) ++
        [nl],
    Spec = spec($pred, severity_error, phase_type_check, Context, Pieces).

report_error_undef_event_arity(Context, EventName, EventArgTypes, Args)
        = Spec :-
    pred_form_arity(ActualArity) = arg_list_arity(Args),
    pred_form_arity(ExpectedArity) = arg_list_arity(EventArgTypes),
    Pieces = [words("Error:")] ++
        arity_error_to_pieces(pf_predicate, ActualArity, [ExpectedArity]) ++
        [words("in")] ++
        color_as_subject([words("event"), quote(EventName), suffix(".")]) ++
        [nl],
    Spec = spec($pred, severity_error, phase_type_check, Context, Pieces).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% The implementation of maybe_report_no_clauses*.
%

maybe_report_no_clauses(ModuleInfo, PredId, PredInfo) = Specs :-
    ShouldReport = should_report_no_clauses(ModuleInfo, PredInfo),
    (
        ShouldReport = yes,
        PredPieces = describe_one_pred_name(ModuleInfo, yes(color_subject),
            should_not_module_qualify, [], PredId),
        Pieces = [words("Error:")] ++ PredPieces ++
            color_as_incorrect([words("has no clauses.")]) ++
            [nl],
        pred_info_get_context(PredInfo, Context),
        Spec = spec($pred, severity_error, phase_type_check, Context, Pieces),
        Specs = [Spec]
    ;
        ShouldReport = no,
        Specs = []
    ).

maybe_report_no_clauses_stub(ModuleInfo, PredId, PredInfo) = Specs :-
    ShouldReport = should_report_no_clauses(ModuleInfo, PredInfo),
    (
        ShouldReport = yes,
        module_info_get_globals(ModuleInfo, Globals),
        globals.lookup_bool_option(Globals, warn_stubs, WarnStubs),
        (
            WarnStubs = yes,
            PredPieces = describe_one_pred_name(ModuleInfo,
                yes(color_subject), should_not_module_qualify, [], PredId),
            Pieces = [words("Warning:")] ++ PredPieces ++
                color_as_incorrect([words("has no clauses.")]) ++ [nl],
            pred_info_get_context(PredInfo, Context),
            Spec = spec($pred, severity_warning(warn_stubs), phase_type_check,
                Context, Pieces),
            Specs = [Spec]
        ;
            WarnStubs = no,
            Specs = []
        )
    ;
        ShouldReport = no,
        Specs = []
    ).

:- func should_report_no_clauses(module_info, pred_info) = bool.

should_report_no_clauses(ModuleInfo, PredInfo) = ShouldReport :-
    module_info_get_int_bad_clauses(ModuleInfo, IntBadClauses),
    module_info_get_name(ModuleInfo, ModuleName),
    pred_info_get_module_name(PredInfo, PredModuleName),
    pred_info_get_name(PredInfo, PredName),
    UserArity = pred_info_user_arity(PredInfo),
    pred_info_get_is_pred_or_func(PredInfo, PredOrFunc),
    SymName = qualified(ModuleName, PredName),
    Id = pred_pf_name_arity(PredOrFunc, SymName, UserArity),
    pred_info_get_markers(PredInfo, PredMarkers),
    ( if
        ( set.contains(IntBadClauses, Id)
        ; marker_is_present(PredMarkers, marker_fact_table_semantic_errors)
        ; marker_is_present(PredMarkers, marker_no_pred_decl)
        ; ModuleName \= PredModuleName
        )
    then
        ShouldReport = no
    else
        ShouldReport = yes
    ).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
%
% Utility predicates useful in more than one of the exported predicates.
%

:- func find_possible_pf_missing_module_qualifiers(predicate_table,
    pred_or_func, sym_name) = list(module_name).

find_possible_pf_missing_module_qualifiers(PredicateTable,
        PredOrFunc, SymName) = ModuleNames :-
    predicate_table_lookup_pf_raw_name(PredicateTable, PredOrFunc,
        unqualify_name(SymName), PredIds),
    list.foldl(accumulate_matching_pf_module_names(PredicateTable, SymName),
        PredIds, [], ModuleNames).

:- pred accumulate_matching_pf_module_names(predicate_table::in, sym_name::in,
    pred_id::in, list(module_name)::in, list(module_name)::out) is det.

accumulate_matching_pf_module_names(PredicateTable, SymName, PredId,
        !ModuleNames) :-
    predicate_table_get_pred_id_table(PredicateTable, PredIdTable),
    map.lookup(PredIdTable, PredId, PredInfo),
    pred_info_get_module_name(PredInfo, ModuleName),
    (
        SymName = unqualified(_),
        !:ModuleNames = [ModuleName | !.ModuleNames]
    ;
        SymName = qualified(SymModuleName, _),
        ( if partial_sym_name_matches_full(SymModuleName, ModuleName) then
            !:ModuleNames = [ModuleName | !.ModuleNames]
        else
            true
        )
    ).

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

:- func report_any_missing_module_qualifiers(type_error_clause_context,
    prog_context, string, set(module_name)) = list(error_msg).

report_any_missing_module_qualifiers(ClauseContext, Context,
        ItemName, ModuleNamesSet0) = Msgs :-
    % We take a set of module names instead of a list, because we want
    % to report the name of each module just once, even if it defines
    % more than one entity with the name that got the error.
    % (Our caller can put the module name into the set more than once:
    % once for each such entity.)

    % For entities defined in the current module and in its ancestors,
    % access via use_module vs import_module is irrelevant.
    ModuleInfo = ClauseContext ^ tecc_module_info,
    module_info_get_name(ModuleInfo, ModuleName),
    set.delete_list([ModuleName | get_ancestors(ModuleName)],
        ModuleNamesSet0, ModuleNamesSet1),
    % Users are not supposed to know about private_builtin.m at all.
    set.delete(mercury_private_builtin_module,
        ModuleNamesSet1, ModuleNamesSet),
    set.to_sorted_list(ModuleNamesSet, ModuleNames),
    (
        ModuleNames = [],
        Msgs = []
    ;
        ModuleNames = [HeadModuleName | TailModuleNames],
        IsDefinedInPieces =
            [words("That"), words(ItemName), words("is defined in")],
        (
            TailModuleNames = [],
            ModuleNamesPieces =
                color_as_subject([qual_sym_name(HeadModuleName), suffix(",")]),
            ModulesPieces = [words("module")] ++ ModuleNamesPieces,
            NoImportsPieces =
                [words("which does not have an"),
                decl("import_module"), words("declaration.")]
        ;
            TailModuleNames = [_ | TailTailModuleNames],
            ModuleNamePieces =
                list.map(func(MN) = qual_sym_name(MN), ModuleNames),
            ModuleNamesPieces =
                piece_list_to_color_pieces(color_subject, "and",
                    [suffix(",")], ModuleNamePieces),
            ModulesPieces = [words("modules")] ++ ModuleNamesPieces,
            (
                TailTailModuleNames = [],
                NoImportsPieces =
                    [words("neither of which has an"),
                    decl("import_module"), words("declaration.")]
            ;
                TailTailModuleNames = [_ | _],
                NoImportsPieces =
                    [words("none of which have"),
                    decl("import_module"), words("declarations."), nl]
            )
        ),
        MainPieces = IsDefinedInPieces ++ ModulesPieces ++
            color_as_hint(NoImportsPieces) ++ [nl],
        MainMsg = msg(Context, MainPieces),
        VerbosePieces = [words("Note that symbols defined in modules"),
            words("accessed via"), decl("use_module"), words("declarations"),
            words("must always be fully module qualified."), nl],
        VerboseMsg = simple_msg(Context,
            [verbose_only(verbose_once, VerbosePieces)]),
        Msgs = [MainMsg, VerboseMsg]
    ).

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

:- pred maybe_report_missing_import_addendum(type_error_clause_context::in,
    module_name::in, list(format_piece)::out, list(module_name)::out)
    is det.

maybe_report_missing_import_addendum(ClauseContext, ModuleQualifier,
        Pieces, MissingImportModules) :-
    % First check if this module wasn't imported.
    ModuleInfo = ClauseContext ^ tecc_module_info,
    module_info_get_visible_modules(ModuleInfo, VisibleModules),

    set.filter(partial_sym_name_matches_full(ModuleQualifier),
        VisibleModules, MatchingVisibleModules),
    ( if set.is_empty(MatchingVisibleModules) then
        % The module qualifier does not match any of the visible modules,
        % so we report that the module has not been imported.
        Pieces = [nl,
            words("(The module")] ++
            color_as_subject([qual_sym_name(ModuleQualifier)]) ++
            color_as_hint([words("has not been imported.)")]) ++
            [nl],
        MissingImportModules = [ModuleQualifier]
    else
        % The module qualifier matches one or more of the visible modules.
        % But maybe the user forgot to import the parent module(s) of that
        % module...
        find_unimported_ancestors(VisibleModules, MatchingVisibleModules,
            UnimportedAncestorsSet),
        set.to_sorted_list(UnimportedAncestorsSet, UnimportedAncestors),
        (
            UnimportedAncestors = [_ | _],
            Pieces = report_unimported_ancestors(UnimportedAncestors),
            % Since ModuleQualifier has unimported parents, its own import,
            % if any, has no effect.
            ModuleQualifierList = sym_name_to_list(ModuleQualifier),
            AddAncestor =
                ( func(AncestorMN) = MN :-
                    AncestorMNList = sym_name_to_list(AncestorMN),
                    FullList = AncestorMNList ++ ModuleQualifierList,
                    det_list_to_sym_name(FullList, MN)
                ),
            MissingImportModules =
                [ModuleQualifier | list.map(AddAncestor, UnimportedAncestors)]
        ;
            UnimportedAncestors = [],
            Pieces = [],
            MissingImportModules = []
        )
    ).

    % Return all the ancestors of modules in MatchingModuleNames
    % which are not imported (i.e. not visible).
    %
:- pred find_unimported_ancestors(set(module_name)::in, set(module_name)::in,
    set(module_name)::out) is det.

find_unimported_ancestors(VisibleModules, MatchingModuleNames,
        UnimportedAncestors) :-
    MatchingModuleNamesAncestorSets =
        set.map(get_ancestors_set, MatchingModuleNames),
    set.power_union(MatchingModuleNamesAncestorSets,
        MatchingModuleNamesAncestors),
    set.difference(MatchingModuleNamesAncestors, VisibleModules,
        UnimportedAncestors).

:- func report_unimported_ancestors(list(module_name))
    = list(format_piece).

report_unimported_ancestors(UnimportedAncestors) = Pieces :-
    UnimportedAncestorPieces =
        list.map((func(M) = qual_sym_name(M)), UnimportedAncestors),
    UnimportedAncestorListPieces =
        piece_list_to_color_pieces(color_subject, "and", [],
            UnimportedAncestorPieces),
    ( if UnimportedAncestors = [_] then
        Pieces = [words("(The possible parent module")] ++
            UnimportedAncestorListPieces ++
            color_as_incorrect([words("has not been imported.)")]) ++
            [nl]
    else
        Pieces = [words("(The possible parent modules")] ++
            UnimportedAncestorListPieces ++
            color_as_incorrect([words("have not been imported.)")]) ++
            [nl]
    ).

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

    % arity_error_to_pieces(PredOrFunc, Arity, CorrectArities):
    %
    % Return a description for the error message
    % "wrong number of arguments (<Arity>; should be <CorrectArities>)",
    % adjusting `Arity' and `CorrectArities' if `MaybePredOrFunc' is
    % `yes(function)'.
    %
:- func arity_error_to_pieces(pred_or_func, int, list(int)) =
    list(format_piece).

arity_error_to_pieces(PredOrFunc, Arity0, Arities0) = Pieces :-
    (
        PredOrFunc = pf_predicate,
        Arity = Arity0,
        Arities = Arities0
    ;
        PredOrFunc = pf_function,
        % Adjust arities for functions.
        adjust_func_arity(pf_function, Arity, Arity0),
        ReverseAdjust =
            ( pred(OtherArity0::in, OtherArity::out) is det :-
                adjust_func_arity(pf_function, OtherArity, OtherArity0)
            ),
        list.map(ReverseAdjust, Arities0, Arities)
    ),
    ActualArityPieces = color_as_incorrect([suffix(int_to_string(Arity))]),
    ExpectedArityPieces = list.map((func(N) = int_fixed(N)), Arities),
    ExpectedAritiesPieces = piece_list_to_color_pieces(color_correct, "or",
        [], ExpectedArityPieces),
    Pieces = color_as_incorrect([words("wrong number of arguments")]) ++
        [prefix("(") | ActualArityPieces] ++ [suffix(";"),
        words("should be") | ExpectedAritiesPieces] ++ [suffix(")")].

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

:- pred get_known_pred_info_names(predicate_table::in, pred_or_func::in,
    list(string)::out) is det.

get_known_pred_info_names(PredicateTable, RequiredPredOrFunc, KnownNames) :-
    predicate_table_get_pred_id_table(PredicateTable, PredIdTable),
    map.values(PredIdTable, PredInfos),
    list.foldl(acc_known_pred_info_names(RequiredPredOrFunc), PredInfos,
        [], KnownNames0),
    list.sort_and_remove_dups(KnownNames0, KnownNames).

:- pred acc_known_pred_info_names(pred_or_func::in, pred_info::in,
    list(string)::in, list(string)::out) is det.

acc_known_pred_info_names(RequiredPredOrFunc, PredInfo, !KnownNames) :-
    pred_info_get_is_pred_or_func(PredInfo, PredOrFunc),
    % Note that we don't care whether PredInfo is valid or not; even if
    % it is invalid, it could be the one the programmer intended to reference.
    ( if PredOrFunc = RequiredPredOrFunc then
        pred_info_get_name(PredInfo, Name),
        !:KnownNames = [Name | !.KnownNames]
    else
        true
    ).

%---------------------------------------------------------------------------%
:- end_module check_hlds.typecheck_error_undef.
%---------------------------------------------------------------------------%
