Files
mercury/compiler/error_util.m
Zoltan Somogyi 823de2d37b Require warning/info messages to specify an option.
The objective of this step is two-fold:

- to fix --inhibit-warnings, making it shut up all warning
  and informational messages; and

- to ensure that it *stays* fixed, even when after new diagnostics
  are added.

As part of this fix, this diff adds a whole bunch of new warning
options, in order to control the warnings that previously were
not controlled by any option. (There was no need for new
informational options.)

As it happens, we have long used severity_informational for messages
that did not report any information about the code being compiled,
but to report actions that the compiler was taking. Create a new
option category, oc_report, for the new options that now control
those diagnostics.

---------------------

compiler/error_spec.m:
    Change severity_warning and severity_informational to take an option
    as as argument. The semantics is that the diagnostic in which
    the severity occurs is conditional on that option, meaning that
    it is printed only if that option is set to "yes".

    Delete the severity_conditional function symbol from the severity
    type, since the mechanism just above handles its only use case.

    Define subtypes to represent error_specs in a standard form.

compiler/error_sort.m:
    Provide operations to convert error specs into their standard form.

    Make the sorting operation itself operate on the standard form.

compiler/write_error_spec.m:
    Convert error_specs to standard form before writing them out,
    in order to avoid duplicating the code for their standardization.

    Change the code that writes out error_specs to operate on the
    standard form. Implement the test implicit in the warning and
    and informational severities in this code.

compiler/error_util.m:
compiler/compiler_util.m:
    Delete operations that do not make sense with the new severity type.

---------------------

compiler/options.m:
    Add new options to control all the previously-uncontrolled
    warning and informational messages.

NEWS.md:
    Announce the *public* new options.

compiler/option_categories.m:
compiler/print_help.m:
    Add the new option category, and fake-include it in the help text
    and the user guide. (The inclusion is fake because none of the
    options in the new category are user visible, meaning the section
    containing them is not visible either.)

---------------------

compiler/det_infer_goal.m:
    Start a severity warning diagnostic with "Warning:"
    instead of "Error:".

compiler/mark_trace_goals.m:
    Fix an incorrect error message.

compiler/purity.m:
    Replace a correct/incorrect color pair with two inconsistent colors,
    because there is a reasonable probability of each one being right.

---------------------

compiler/accumulator.m:
compiler/add_clause.m:
compiler/add_mode.m:
compiler/add_pragma.m:
compiler/add_pragma_tabling.m:
compiler/add_pred.m:
compiler/add_type.m:
compiler/check_module_interface.m:
compiler/check_type_inst_mode_defns.m:
compiler/check_typeclass.m:
compiler/color_schemes.m:
compiler/common.m:
compiler/convert_import_use.m:
compiler/convert_parse_tree.m:
compiler/dead_proc_elim.m:
compiler/det_check_proc.m:
compiler/det_check_switch.m:
compiler/det_infer_goal.m:
compiler/du_type_layout.m:
compiler/format_call_errors.m:
compiler/grab_modules.m:
compiler/hlds_call_tree.m:
compiler/inst_check.m:
compiler/introduce_parallelism.m:
compiler/make_hlds_error.m:
compiler/make_hlds_warn.m:
compiler/mark_tail_calls.m:
compiler/mark_trace_goals.m:
compiler/mercury_compile_main.m:
compiler/mercury_compile_make_hlds.m:
compiler/mode_errors.m:
compiler/modes.m:
compiler/module_qual.qual_errors.m:
compiler/opt_deps_spec.m:
compiler/options_file.m:
compiler/parse_goal.m:
compiler/post_term_analysis.m:
compiler/post_typecheck.m:
compiler/pre_typecheck.m:
compiler/purity.m:
compiler/read_modules.m:
compiler/recompilation.check.m:
compiler/simplify_goal.m:
compiler/simplify_goal_call.m:
compiler/simplify_goal_disj.m:
compiler/simplify_goal_ite.m:
compiler/split_parse_tree_src.m:
compiler/state_var.m:
compiler/stratify.m:
compiler/style_checks.m:
compiler/superhomogeneous.m:
compiler/table_gen.m:
compiler/term_constr_errors.m:
compiler/term_errors.m:
compiler/termination.m:
compiler/typecheck_clauses.m:
compiler/typecheck_error_overload.m:
compiler/typecheck_error_undef.m:
compiler/typecheck_errors.m:
compiler/typecheck_msgs.m:
compiler/unused_args.m:
compiler/unused_imports.m:
compiler/warn_unread_modules.m:
compiler/write_module_interface_files.m:
    Conform to the changes above, mostly by either

    - adding an option to all warning and informational messages,
      sometimes using existing warning options and sometimes new ones,
      or

    - turning already explicitly-conditional-on-an-option messages
      into implicitly-conditional-on-that-option messages.

---------------------

tests/invalid/one_member.m:
    Conform to the change in det_infer_goal.m.

tests/invalid/require_tailrec_1.err_exp:
tests/invalid/require_tailrec_2.err_exp:
    Actually obey the options for these modules in Mercury.options.

tests/invalid_purity/purity.err_exp:
tests/warnings/purity_warnings.err_exp:
    Conform to the change in purity.m.

tests/warnings/moved_trace_goal.err_exp:
    Conform to the change in mark_trace_goals.m.

tests/warnings/help_text.err_exp:
    Expect the documentation of all the new options.
2025-08-18 12:07:38 +02:00

543 lines
20 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1997-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: error_util.m.
% Main author: zs.
%
% This module contains utility predicates and functions operating on
% error_specs.
%
%---------------------------------------------------------------------------%
:- module parse_tree.error_util.
:- interface.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.options.
:- import_module parse_tree.error_spec.
:- import_module bool.
:- import_module list.
:- import_module maybe.
%---------------------------------------------------------------------------%
% Would trying to print the given spec result in any output?
% The answer can be "no" if all parts of the error_spec are
% under a condition that happens to be false.
%
:- pred does_spec_print_anything(globals::in, error_spec::in) is semidet.
%---------------------------------------------------------------------------%
% Return the worst of two actual severities.
%
:- func worst_severity(actual_severity, actual_severity)
= actual_severity.
% Compute the actual severity of an error_spec
% (if it actually prints anything).
%
:- func actual_spec_severity(globals, error_spec) = maybe(actual_severity).
:- func actual_spec_severity_opt_table(option_table, error_spec) =
maybe(actual_severity).
% Succeeds if and only if the given error_spec has a severity,
% and it is severity_error.
%
:- pred actual_spec_severity_is_error(globals::in, error_spec::in) is semidet.
% Compute the worst actual severity (if any) occurring in a list of
% error_specs.
%
:- func worst_severity_in_specs(globals, list(error_spec))
= maybe(actual_severity).
:- func worst_severity_in_specs_opt_table(option_table, list(error_spec))
= maybe(actual_severity).
% Return `yes' if the given list contains error_specs whose actual severity
% is actual_severity_error.
%
:- func contains_errors(globals, list(error_spec)) = bool.
:- func contains_errors_option_table(option_table, list(error_spec)) = bool.
% Return `yes' if the given list contains error_specs whose actual severity
% is actual_severity_error or actual_severity_warning.
%
:- func contains_errors_and_or_warnings(globals, list(error_spec)) = bool.
:- func contains_errors_and_or_warnings_opt_table(option_table,
list(error_spec)) = bool.
% If --halt-at-warn is not set, then return `yes' if the given list
% contains error_specs whose actual severity is actual_severity_error.
%
% If --halt-at-warn is set, then return `yes' if the given list
% contains error_specs whose actual severity is either
% actual_severity_error or actual_severity_warning.
%
:- func contains_errors_or_warnings_treated_as_errors(globals,
list(error_spec)) = bool.
:- func contains_errors_or_warnings_treated_as_errors_opt_table(option_table,
list(error_spec)) = bool.
%---------------------------------------------------------------------------%
% Given a function that constructs lists of format pieces when given
% an item and a suffix, return a list of format pieces which
%
% - contains the output of the function for all the items in the list,
% - with these outputs being in a sorted order (sorted on format_pieces),
% - with the output for all items ending with a comma and a newline,
% - with the exception of the last output, which has a period, not a comma.
%
:- pred construct_sorted_line_pieces(
func(list(format_piece), T) = list(format_piece)::in,
list(T)::in, list(format_piece)::out) is det.
%---------------------------------------------------------------------------%
:- func start_each_msg_with_blank_line(list(error_msg)) = list(error_msg).
%---------------------------------------------------------------------------%
% Delete all the given error_specs, which are supposed to have been
% gathered during the process that generates the contents of an interface
% file, if halt_at_invalid_interface is not set.
%
% Even if it is set, delete any conditional error specs whose conditions
% are false.
%
:- pred filter_interface_generation_specs(globals::in,
list(error_spec)::in, list(error_spec)::out) is det.
%---------------------------------------------------------------------------%
%
% The error_spec_accumulator type can be used to accumulate errors for
% multiple modes of a predicate. accumulate_error_specs_for_proc will
% eliminate warnings that should only be reported if they occur in every mode,
% but don't occur in every mode.
:- type error_spec_accumulator.
:- func init_error_spec_accumulator = error_spec_accumulator.
:- pred accumulate_error_specs_for_proc(list(error_spec)::in,
error_spec_accumulator::in, error_spec_accumulator::out) is det.
:- func error_spec_accumulator_to_list(error_spec_accumulator) =
list(error_spec).
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module getopt.
:- import_module pair.
:- import_module set.
:- import_module term_context.
%---------------------------------------------------------------------------%
does_spec_print_anything(Globals, Spec) :-
does_spec_print_anything_2(Globals, Spec) = yes.
:- func does_spec_print_anything_2(globals, error_spec) = bool.
does_spec_print_anything_2(Globals, Spec) = Prints :-
(
( Spec = spec(_, _, _, _, _)
; Spec = no_ctxt_spec(_, _, _, _)
),
Prints = yes
;
Spec = error_spec(_, _, _, Msgs),
PrintsList = list.map(does_msg_print_anything(Globals), Msgs),
bool.or_list(PrintsList, Prints)
;
Spec = conditional_spec(_, Option, MatchValue, _, _, Msgs),
globals.lookup_bool_option(Globals, Option, OptionValue),
( if OptionValue = MatchValue then
PrintsList = list.map(does_msg_print_anything(Globals), Msgs),
bool.or_list(PrintsList, Prints)
else
Prints = no
)
).
:- func does_msg_print_anything(globals, error_msg) = bool.
does_msg_print_anything(Globals, Msg) = Prints :-
(
( Msg = msg(_, _)
; Msg = no_ctxt_msg(_)
),
Prints = yes
;
Msg = blank_msg(_),
Prints = no
;
( Msg = simple_msg(_, MsgComponents)
; Msg = error_msg(_, _, _, MsgComponents)
),
PrintsList = list.map(does_msg_component_print_anything(Globals),
MsgComponents),
bool.or_list(PrintsList, Prints)
).
:- func does_msg_component_print_anything(globals, error_msg_component) = bool.
does_msg_component_print_anything(Globals, MsgComponent) = Prints :-
(
( MsgComponent = always(_)
; MsgComponent = verbose_only(_, _)
; MsgComponent = verbose_and_nonverbose(_, _)
),
Prints = yes
;
MsgComponent = option_is_set(Option, MatchValue, MsgComponents),
globals.lookup_bool_option(Globals, Option, OptionValue),
( if OptionValue = MatchValue then
PrintsList = list.map(does_msg_component_print_anything(Globals),
MsgComponents),
bool.or_list(PrintsList, Prints)
else
Prints = no
)
).
%---------------------------------------------------------------------------%
worst_severity(actual_severity_error, actual_severity_error) =
actual_severity_error.
worst_severity(actual_severity_error, actual_severity_warning) =
actual_severity_error.
worst_severity(actual_severity_error, actual_severity_informational) =
actual_severity_error.
worst_severity(actual_severity_warning, actual_severity_error) =
actual_severity_error.
worst_severity(actual_severity_warning, actual_severity_warning) =
actual_severity_warning.
worst_severity(actual_severity_warning, actual_severity_informational) =
actual_severity_warning.
worst_severity(actual_severity_informational, actual_severity_error) =
actual_severity_error.
worst_severity(actual_severity_informational, actual_severity_warning) =
actual_severity_warning.
worst_severity(actual_severity_informational, actual_severity_informational) =
actual_severity_informational.
%---------------------%
actual_spec_severity(Globals, Spec) = MaybeSeverity :-
globals.get_options(Globals, OptionTable),
MaybeSeverity = actual_spec_severity_opt_table(OptionTable, Spec).
actual_spec_severity_opt_table(OptionTable, Spec) = MaybeActualSeverity :-
(
( Spec = error_spec(_, Severity, _, _)
; Spec = spec(_, Severity, _, _, _)
; Spec = no_ctxt_spec(_, Severity, _, _)
),
severity_to_maybe_actual_severity(OptionTable, Severity,
MaybeActualSeverity)
;
Spec = conditional_spec(_, Option, MatchValue, Severity, _, _),
getopt.lookup_bool_option(OptionTable, Option, Value),
( if Value = MatchValue then
severity_to_maybe_actual_severity(OptionTable, Severity,
MaybeActualSeverity)
else
MaybeActualSeverity = no
)
).
:- pred severity_to_maybe_actual_severity(option_table::in,
error_severity::in, maybe(actual_severity)::out) is det.
severity_to_maybe_actual_severity(OptionTable, Severity,
MaybeActualSeverity) :-
(
Severity = severity_error,
MaybeActualSeverity = yes(actual_severity_error)
;
Severity = severity_warning(Option),
getopt.lookup_bool_option(OptionTable, Option, OptionValue),
(
OptionValue = yes,
MaybeActualSeverity = yes(actual_severity_warning)
;
OptionValue = no,
MaybeActualSeverity = no
)
;
Severity = severity_informational(Option),
getopt.lookup_bool_option(OptionTable, Option, OptionValue),
(
OptionValue = yes,
MaybeActualSeverity = yes(actual_severity_informational)
;
OptionValue = no,
MaybeActualSeverity = no
)
).
actual_spec_severity_is_error(Globals, Spec) :-
actual_spec_severity(Globals, Spec) = yes(actual_severity_error).
%---------------------%
worst_severity_in_specs(Globals, Specs) = MaybeWorst :-
globals.get_options(Globals, OptionTable),
worst_severity_in_specs_loop(OptionTable, Specs, no, MaybeWorst).
worst_severity_in_specs_opt_table(OptionTable, Specs) = MaybeWorst :-
worst_severity_in_specs_loop(OptionTable, Specs, no, MaybeWorst).
:- pred worst_severity_in_specs_loop(option_table::in, list(error_spec)::in,
maybe(actual_severity)::in, maybe(actual_severity)::out) is det.
worst_severity_in_specs_loop(_OptionTable, [], !MaybeWorst).
worst_severity_in_specs_loop(OptionTable, [Spec | Specs], !MaybeWorst) :-
MaybeThis = actual_spec_severity_opt_table(OptionTable, Spec),
(
!.MaybeWorst = no,
!:MaybeWorst = MaybeThis
;
!.MaybeWorst = yes(Worst),
(
MaybeThis = no
;
MaybeThis = yes(This),
!:MaybeWorst = yes(worst_severity(Worst, This))
)
),
worst_severity_in_specs_loop(OptionTable, Specs, !MaybeWorst).
%---------------------%
contains_errors(Globals, Specs) = Errors :-
globals.get_options(Globals, OptionTable),
Errors = contains_errors_option_table(OptionTable, Specs).
contains_errors_option_table(OptionTable, Specs) = Errors :-
MaybeWorstActual = worst_severity_in_specs_opt_table(OptionTable, Specs),
(
MaybeWorstActual = no,
Errors = no
;
MaybeWorstActual = yes(WorstActual),
(
WorstActual = actual_severity_error,
Errors = yes
;
( WorstActual = actual_severity_warning
; WorstActual = actual_severity_informational
),
Errors = no
)
).
%---------------------%
contains_errors_and_or_warnings(Globals, Specs) = ErrorsOrWarnings :-
globals.get_options(Globals, OptionTable),
ErrorsOrWarnings =
contains_errors_and_or_warnings_opt_table(OptionTable, Specs).
contains_errors_and_or_warnings_opt_table(OptionTable, Specs) =
ErrorsOrWarnings :-
MaybeWorstActual = worst_severity_in_specs_opt_table(OptionTable, Specs),
(
MaybeWorstActual = no,
ErrorsOrWarnings = no
;
MaybeWorstActual = yes(WorstActual),
(
( WorstActual = actual_severity_error
; WorstActual = actual_severity_warning
),
ErrorsOrWarnings = yes
;
WorstActual = actual_severity_informational,
ErrorsOrWarnings = no
)
).
%---------------------%
contains_errors_or_warnings_treated_as_errors(Globals, Specs) = Halt :-
globals.get_options(Globals, OptionTable),
Halt = contains_errors_or_warnings_treated_as_errors_opt_table(OptionTable,
Specs).
contains_errors_or_warnings_treated_as_errors_opt_table(OptionTable, Specs)
= Halt :-
MaybeWorstActual = worst_severity_in_specs_opt_table(OptionTable, Specs),
(
MaybeWorstActual = no,
Halt = no
;
MaybeWorstActual = yes(WorstActual),
(
WorstActual = actual_severity_error,
Halt = yes
;
WorstActual = actual_severity_warning,
getopt.lookup_bool_option(OptionTable, halt_at_warn, HaltAtWarn),
(
HaltAtWarn = yes,
Halt = yes
;
HaltAtWarn = no,
Halt = no
)
;
WorstActual = actual_severity_informational,
Halt = no
)
).
%---------------------------------------------------------------------------%
construct_sorted_line_pieces(MakeItemPiecesFunc, Items, Pieces) :-
(
Items = [],
Pieces = []
;
Items = [_ | _],
% We want the output piece lists to be sorted, and we want to
% - add a comma after the pieces of all items but the last, and
% - add a period after the pieces of the last item.
% Since the pieces we construct for each item are far from guaranteed
% to be in the same order as the items, we cannot identify which
% item's pieces should be last by looking at only Items.
list.map(construct_sorted_line_pieces_pair(MakeItemPiecesFunc),
Items, ItemPiecesPairs),
list.sort(ItemPiecesPairs, SortedItemPiecesPairs),
list.det_split_last(SortedItemPiecesPairs, NonLastPiecesPairs,
_LastPieces - LastItem),
list.map(pair.fst, NonLastPiecesPairs, NonLastPiecesList),
list.condense(NonLastPiecesList, NonLastPieces),
LastPieces = MakeItemPiecesFunc([suffix("."), nl], LastItem),
Pieces = NonLastPieces ++ LastPieces
).
:- pred construct_sorted_line_pieces_pair(
func(list(format_piece), T) = list(format_piece)::in,
T::in, pair(list(format_piece), T)::out) is det.
construct_sorted_line_pieces_pair(MakeItemPiecesFunc, Item,
ItemPieces - Item) :-
ItemPieces = MakeItemPiecesFunc([suffix(","), nl], Item).
%---------------------------------------------------------------------------%
start_each_msg_with_blank_line([]) = [].
start_each_msg_with_blank_line([HeadMsg0 | TailMsgs0]) = Msgs :-
extract_msg_maybe_context(HeadMsg0, MaybeContext),
TailMsgs = start_each_msg_with_blank_line(TailMsgs0),
Msgs = [blank_msg(MaybeContext), HeadMsg0 | TailMsgs].
%---------------------------------------------------------------------------%
filter_interface_generation_specs(Globals, Specs, SpecsToPrint) :-
globals.lookup_bool_option(Globals,
halt_at_invalid_interface, HaltInvalidInterface),
(
HaltInvalidInterface = yes,
list.filter(does_spec_print_anything(Globals), Specs, SpecsToPrint)
;
HaltInvalidInterface = no,
SpecsToPrint = []
).
%---------------------------------------------------------------------------%
:- type error_spec_accumulator == maybe(pair(set(error_spec))).
init_error_spec_accumulator = no.
accumulate_error_specs_for_proc(ProcSpecs, !MaybeSpecs) :-
list.filter(
( pred(Spec::in) is semidet :-
Phase = project_spec_phase(Spec),
ModeReportControl = get_maybe_mode_report_control(Phase),
ModeReportControl = yes(report_only_if_in_all_modes)
), ProcSpecs, ProcAllModeSpecs, ProcAnyModeSpecs),
ProcAnyModeSpecSet = set.list_to_set(ProcAnyModeSpecs),
ProcAllModeSpecSet = set.list_to_set(ProcAllModeSpecs),
(
!.MaybeSpecs = yes(AnyModeSpecSet0 - AllModeSpecSet0),
set.union(AnyModeSpecSet0, ProcAnyModeSpecSet, AnyModeSpecSet),
set.intersect(AllModeSpecSet0, ProcAllModeSpecSet, AllModeSpecSet),
!:MaybeSpecs = yes(AnyModeSpecSet - AllModeSpecSet)
;
!.MaybeSpecs = no,
!:MaybeSpecs = yes(ProcAnyModeSpecSet - ProcAllModeSpecSet)
).
:- func project_spec_phase(error_spec) = error_phase.
project_spec_phase(Spec) = Phase :-
(
Spec = error_spec(_, _, Phase, _)
;
Spec = spec(_, _, Phase, _, _)
;
Spec = no_ctxt_spec(_, _, Phase, _)
;
Spec = conditional_spec(_, _, _, _, Phase, _)
).
error_spec_accumulator_to_list(no) = [].
error_spec_accumulator_to_list(yes(AnyModeSpecSet - AllModeSpecSet)) =
set.to_sorted_list(set.union(AnyModeSpecSet, AllModeSpecSet)).
:- func get_maybe_mode_report_control(error_phase) =
maybe(mode_report_control).
get_maybe_mode_report_control(phase_options) = no.
get_maybe_mode_report_control(phase_check_libs) = no.
get_maybe_mode_report_control(phase_make_target) = no.
get_maybe_mode_report_control(phase_make_int) = no.
get_maybe_mode_report_control(phase_find_files(_)) = no.
get_maybe_mode_report_control(phase_read_files) = no.
get_maybe_mode_report_control(phase_module_name) = no.
get_maybe_mode_report_control(phase_t2pt) = no.
get_maybe_mode_report_control(phase_tim_check) = no.
get_maybe_mode_report_control(phase_tim_check_invalid_type) = no.
get_maybe_mode_report_control(phase_tim_check_invalid_inst_mode)
= no.
get_maybe_mode_report_control(phase_type_repn) = no.
get_maybe_mode_report_control(phase_pt2h) = no.
get_maybe_mode_report_control(phase_expand_types) = no.
get_maybe_mode_report_control(phase_type_check) = no.
get_maybe_mode_report_control(phase_inst_check) = no.
get_maybe_mode_report_control(phase_polymorphism) = no.
get_maybe_mode_report_control(phase_mode_check(Control)) = yes(Control).
get_maybe_mode_report_control(phase_purity_check) = no.
get_maybe_mode_report_control(phase_detism_check) = no.
get_maybe_mode_report_control(phase_fact_table_check) = no.
get_maybe_mode_report_control(phase_oisu_check) = no.
get_maybe_mode_report_control(phase_simplify(Control)) = yes(Control).
get_maybe_mode_report_control(phase_direct_arg_in_out) = no.
get_maybe_mode_report_control(phase_style) = no.
get_maybe_mode_report_control(phase_dead_code) = no.
get_maybe_mode_report_control(phase_termination_analysis) = no.
get_maybe_mode_report_control(phase_accumulator_intro) = no.
get_maybe_mode_report_control(phase_auto_parallelism) = no.
get_maybe_mode_report_control(phase_interface_gen) = no.
get_maybe_mode_report_control(phase_code_gen) = no.
%---------------------------------------------------------------------------%
:- end_module parse_tree.error_util.
%---------------------------------------------------------------------------%