Files
mercury/compiler/error_sort.m
Zoltan Somogyi 68c3d4040b Let callers of write_*interface_file* print errors.
compiler/write_module_interface_files.m:
    When the process of generating the contents of an interface file
    finds some errors, don't print those errors immediately. Just return
    them instead, letting the caller decide what to do with them.

    Fix comment rot.

compiler/make.get_module_dep_info.m:
compiler/mercury_compile_main.m:
    Print the error specs returned by the updated predicates (for now,
    at least).

compiler/error_sort.m:
    Fix an issue exposed by the changes above. When comparing two error_specs
    to see which one should be printed first, don't take the lack of a content
    in an error_spec as a sign that it should be printed first. Instead,
    pay attention to the comparison of contexts *only* if both of the
    error_specs being compared actually *have* contexts.

tests/invalid/type_inf_loop.err_exp:
    Expect the contextless message about exceeding the type inference
    iteration limit to come after the other messages, due to the update
    to error_sort.m.

tests/invalid/type_inf_loop.err_exp2:
    Delete this file. It can't match the compiler's current output,
    or (it seems) the output of any compiler version since we switched
    to reporting type errors using error_specs.
2023-10-08 10:11:08 +11:00

352 lines
12 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1997-2012 The University of Melbourne.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: error_sort.m.
% Main author: zs.
%
% This module sorts error_specs and error_msgs.
%
%---------------------------------------------------------------------------%
:- module parse_tree.error_sort.
:- interface.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.options.
:- import_module parse_tree.error_spec.
:- import_module list.
%---------------------------------------------------------------------------%
:- pred sort_error_specs(globals::in,
list(error_spec)::in, list(error_spec)::out) is det.
:- pred sort_error_specs_opt_table(option_table::in,
list(error_spec)::in, list(error_spec)::out) is det.
%---------------------------------------------------------------------------%
:- pred sort_error_msgs(list(error_msg)::in, list(error_msg)::out) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module parse_tree.error_util.
:- import_module parse_tree.prog_data.
:- import_module bool.
:- import_module cord.
:- import_module getopt.
:- import_module maybe.
:- import_module term_context.
%---------------------------------------------------------------------------%
sort_error_specs(Globals, !Specs) :-
globals.get_options(Globals, OptionTable),
sort_error_specs_opt_table(OptionTable, !Specs).
sort_error_specs_opt_table(OptionTable, !Specs) :-
% The purpose of remove_conditionals_in_spec is to remove differences
% between error_specs that exist only in the structure of the error_specs
% themselves, as opposed to the text that we output for them.
%
% For example, the parser can generate two error specs for a bad module
% name that differ in two things.
%
% - The first difference is that one has "severity_error", while the other
% has "severity_conditional(warn_wrong_module_name, yes, severity_error,
% no)". However, since warn_wrong_module_name is yes by default,
% this difference has no effect.
%
% - The second difference is that some error_msg_components consist of
% "always(...)" in one, and "option_is_set(warn_wrong_module_name, yes,
% always(...))" in the other. But if warn_wrong_module_name is yes,
% this difference has no effect either.
%
% (The parser should no longer generate duplicate error messages
% for bad module names, but we still keep this workaround in place,
% since the cost of doing so is trivial.)
%
list.filter_map(remove_conditionals_in_spec(OptionTable), !Specs),
getopt.lookup_bool_option(OptionTable, reverse_error_order,
ReverseErrorOrder),
list.sort_and_remove_dups(
compare_error_specs(OptionTable, ReverseErrorOrder),
!Specs).
:- pred remove_conditionals_in_spec(option_table::in,
error_spec::in, error_spec::out) is semidet.
remove_conditionals_in_spec(OptionTable, Spec0, Spec) :-
require_det (
(
Spec0 = error_spec(Id, Severity0, Phase, Msgs0),
MaybeActualSeverity =
actual_error_severity_opt_table(OptionTable, Severity0),
list.filter_map(remove_conditionals_in_msg(OptionTable),
Msgs0, Msgs)
;
Spec0 = simplest_spec(Id, Severity0, Phase, Context0, Pieces0),
MaybeActualSeverity =
actual_error_severity_opt_table(OptionTable, Severity0),
Msgs = [simplest_msg(Context0, Pieces0)]
;
Spec0 = simplest_no_context_spec(Id, Severity0, Phase, Pieces0),
MaybeActualSeverity =
actual_error_severity_opt_table(OptionTable, Severity0),
Msgs = [simplest_no_context_msg(Pieces0)]
;
Spec0 = conditional_spec(Id, Option, MatchValue,
Severity0, Phase, Msgs0),
getopt.lookup_bool_option(OptionTable, Option, OptionValue),
( if OptionValue = MatchValue then
MaybeActualSeverity =
actual_error_severity_opt_table(OptionTable, Severity0),
Msgs = Msgs0
else
MaybeActualSeverity = no,
Msgs = []
)
)
),
( if
MaybeActualSeverity = yes(ActualSeverity),
Msgs = [_ | _]
then
require_det (
(
ActualSeverity = actual_severity_error,
Severity = severity_error
;
ActualSeverity = actual_severity_warning,
Severity = severity_warning
;
ActualSeverity = actual_severity_informational,
Severity = severity_informational
),
Spec = error_spec(Id, Severity, Phase, Msgs)
)
else
% Spec0 would result in nothing being printed.
fail
).
:- pred remove_conditionals_in_msg(option_table::in,
error_msg::in, error_msg::out) is semidet.
remove_conditionals_in_msg(OptionTable, Msg0, Msg) :-
require_det (
(
Msg0 = simplest_msg(Context, Pieces0),
MaybeContext = yes(Context),
TreatAsFirst = treat_based_on_posn,
ExtraIndent = 0,
Components0 = [always(Pieces0)]
;
Msg0 = simplest_no_context_msg(Pieces0),
MaybeContext = no,
TreatAsFirst = treat_based_on_posn,
ExtraIndent = 0,
Components0 = [always(Pieces0)]
;
Msg0 = simple_msg(Context, Components0),
MaybeContext = yes(Context),
TreatAsFirst = treat_based_on_posn,
ExtraIndent = 0
;
Msg0 = error_msg(MaybeContext, TreatAsFirst, ExtraIndent,
Components0)
),
list.foldl(remove_conditionals_in_msg_component(OptionTable),
Components0, cord.init, ComponentCord),
Components = cord.list(ComponentCord),
Msg = error_msg(MaybeContext, TreatAsFirst, ExtraIndent, Components)
),
% Don't include the Msg if Components is empty.
Components = [_ | _].
:- pred remove_conditionals_in_msg_component(option_table::in,
error_msg_component::in,
cord(error_msg_component)::in, cord(error_msg_component)::out) is det.
remove_conditionals_in_msg_component(OptionTable, Component, !ComponentCord) :-
(
Component = option_is_set(Option, MatchValue, EmbeddedComponents),
% We could recurse down into EmbeddedComponents, but we currently
% have any places in the compiler that can generate two error messages
% that differ only in nested option settings, so there would be
% no point.
getopt.lookup_bool_option(OptionTable, Option, OptionValue),
( if OptionValue = MatchValue then
!:ComponentCord =
!.ComponentCord ++ cord.from_list(EmbeddedComponents)
else
true
)
;
% We don't want to eliminate the verbose only part of a message
% even if verbose_errors isn't set. We want to keep them around
% until we print the component, so that we can record the presence
% of such verbose components, and generate a reminder of their
% existence at the end of the compilation.
%
% Besides, the compiler can't (yet) generate two error_msg_components
% that differ only in the presence of a verbose error.
( Component = always(_)
; Component = verbose_only(_, _)
; Component = verbose_and_nonverbose(_, _)
; Component = print_anything(_)
),
!:ComponentCord = cord.snoc(!.ComponentCord, Component)
).
%---------------------%
:- pred compare_error_specs(option_table::in, bool::in,
error_spec::in, error_spec::in, comparison_result::out) is det.
compare_error_specs(OptionTable, ReverseErrorOrder, SpecA, SpecB, Result) :-
extract_spec_msgs_opt_table(OptionTable, SpecA, MsgsA),
extract_spec_msgs_opt_table(OptionTable, SpecB, MsgsB),
compare_error_msg_lists(ReverseErrorOrder, MsgsA, MsgsB, MsgsResult),
(
MsgsResult = (=),
compare(Result, SpecA, SpecB)
;
( MsgsResult = (>)
; MsgsResult = (<)
),
Result = MsgsResult
).
:- pred compare_error_msg_lists(bool::in,
list(error_msg)::in, list(error_msg)::in, comparison_result::out) is det.
compare_error_msg_lists(ReverseErrorOrder, MsgsA, MsgsB, Result) :-
(
MsgsA = [],
MsgsB = [],
Result = (=)
;
MsgsA = [],
MsgsB = [_ | _],
Result = (<)
;
MsgsA = [_ | _],
MsgsB = [],
Result = (>)
;
MsgsA = [HeadMsgA | TailMsgsA],
MsgsB = [HeadMsgB | TailMsgsB],
compare_error_msgs(ReverseErrorOrder, HeadMsgA, HeadMsgB,
HeadResult),
(
HeadResult = (=),
compare_error_msg_lists(ReverseErrorOrder, TailMsgsA, TailMsgsB,
Result)
;
( HeadResult = (>)
; HeadResult = (<)
),
Result = HeadResult
)
).
%---------------------------------------------------------------------------%
sort_error_msgs(Msgs0, Msgs) :-
list.sort_and_remove_dups(compare_error_msgs(no), Msgs0, Msgs).
:- pred compare_error_msgs(bool::in, error_msg::in, error_msg::in,
comparison_result::out) is det.
compare_error_msgs(ReverseErrorOrder, MsgA, MsgB, Result) :-
MaybeContextA = project_msg_context(MsgA),
MaybeContextB = project_msg_context(MsgB),
% The context comparison makes sense only if both Msgs have a context.
% If one or both Msgs lack a context, then go on to compare the components.
( if
MaybeContextA = yes(ContextA),
MaybeContextB = yes(ContextB)
then
compare(ContextResult, ContextA, ContextB)
else
ContextResult = (=)
),
(
ContextResult = (=),
ComponentsA = project_msg_components(MsgA),
ComponentsB = project_msg_components(MsgB),
compare(ComponentsResult, ComponentsA, ComponentsB),
(
ComponentsResult = (=),
compare(Result, MsgA, MsgB)
;
( ComponentsResult = (>)
; ComponentsResult = (<)
),
Result = ComponentsResult
)
;
ContextResult = (>),
(
ReverseErrorOrder = no,
Result = ContextResult
;
ReverseErrorOrder = yes,
Result = (<)
)
;
ContextResult = (<),
(
ReverseErrorOrder = no,
Result = ContextResult
;
ReverseErrorOrder = yes,
Result = (>)
)
).
:- func project_msg_context(error_msg) = maybe(prog_context).
project_msg_context(Msg) = MaybeContext :-
(
Msg = simplest_msg(Context, _),
MaybeContext = yes(Context)
;
Msg = simplest_no_context_msg(_),
MaybeContext = no
;
Msg = simple_msg(Context, _),
MaybeContext = yes(Context)
;
Msg = error_msg(MaybeContext, _, _, _)
).
:- func project_msg_components(error_msg) = list(error_msg_component).
project_msg_components(Msg) = Components :-
(
( Msg = simplest_msg(_, Pieces)
; Msg = simplest_no_context_msg(Pieces)
),
Components = [always(Pieces)]
;
Msg = simple_msg(_, Components)
;
Msg = error_msg(_, _, _, Components)
).
%---------------------------------------------------------------------------%
:- end_module parse_tree.error_sort.
%---------------------------------------------------------------------------%