[m-rev.] for review: pragma format_call
Julien Fischer
jfischer at opturion.com
Fri Sep 23 12:36:17 AEST 2022
On Tue, 20 Sep 2022, Zoltan Somogyi wrote:
> I have not made any changes to the reference manual since addressing
> the review of its diff.
>
> This diff has a ZZZ in add_pragma.m, on code that checks the status
> of the new pragma the same way that we check the status of other
> declarative pragmas: we generate an error if the predicate (or function)
> to which the pragma applies is exported, but the pragma itself is not exported.
>
> I think this test is wrong. We should generate an error in the *opposite*
> case: the pragma is exported, but the pred/func it applies to is not exported.
We should definitely be doing that.
> The case when the pred/func is exported but the declarative pragma is not
> exported should merit only a warning, because the other modules that
> import that pred/func can survive without knowing that e.g. that pred/func
> is obsolete, or that calls to it can be checked the same way as calls to e.g.
> string.format.
That seems reasonable.
> Add the format_call pragma to the language.
>
> doc/reference_manual.texi:
> NEWS:
> Document and announce the new pragma.
>
> compiler/prog_data_pragma.m:
> compiler/prog_item.m:
> Provide a representation for the new pragma. The part that ends up
> being referred to from the HLDS goes into prog_data_pragma.m,
> the part that is not needed once the HLDS has been constructed
> goes into prog_item.m.
>
>
> tests/warnings/format_call_warning.{m,exp}:
> A new test case to see whether we generate the expected set of error
> messages for incorrect calls to a predicate with a format_call pragma.
...
> diff --git a/compiler/check_pragma_format_call.m b/compiler/check_pragma_format_call.m
> index e69de29bb..611aa17ba 100644
> --- a/compiler/check_pragma_format_call.m
> +++ b/compiler/check_pragma_format_call.m
...
> +:- pred check_for_non_input_arg_nums_in_proc(module_info::in,
> + int::in, pair(proc_id, proc_info)::in, int::in, int::out,
> + list(int)::in, list(int)::out) is det.
> +
> +check_for_non_input_arg_nums_in_proc(ModuleInfo, ArgNum, _ProcId - ProcInfo,
> + !CurModeNum, !BadModeNums) :-
> + proc_info_get_argmodes(ProcInfo, ArgModes),
> + list.det_index1(ArgModes, ArgNum, ArgMode),
> + ( if mode_is_input(ModuleInfo, ArgMode) then
> + true
> + else
> + !:BadModeNums = [!.CurModeNum | !.BadModeNums]
> + ),
> + !:CurModeNum = !.CurModeNum + 1.
> +
> +%---------------------%
> +
> +% XXX should the references to format_string_values below be quoted?
Yes, I think they should be quoted.
> +
> +:- func format_call_error_too_large_arg_num(pred_info, prog_context,
> + int, int, int, int, string) = error_spec.
> +
> +format_call_error_too_large_arg_num(PredInfo, Context, NumFormatArgs,
> + FormatArgNum, MaxArgNum, ArgNum, FirstOrSecond) = Spec :-
> + PredOrFunc = pred_info_is_pred_or_func(PredInfo),
> + Pieces =
> + format_call_error_prelude(PredInfo, NumFormatArgs, FormatArgNum) ++
> + [words("the"), words(FirstOrSecond), words("argument of"),
> + words("format_string_values specifies argument number"),
> + int_fixed(ArgNum), suffix(","), words("but the"), p_or_f(PredOrFunc),
> + words("has only"), int_fixed(MaxArgNum), words("arguments."), nl],
> + Spec = simplest_spec($pred, severity_error, phase_parse_tree_to_hlds,
> + Context, Pieces).
> +
> +:- func format_call_error_too_small_arg_num(pred_info, prog_context,
> + int, int, int, string) = error_spec.
> +
> +format_call_error_too_small_arg_num(PredInfo, Context, NumFormatArgs,
> + FormatArgNum, ArgNum, FirstOrSecond) = Spec :-
> + Pieces =
> + format_call_error_prelude(PredInfo, NumFormatArgs, FormatArgNum) ++
> + [words("the"), words(FirstOrSecond), words("argument of"),
> + words("format_string_values specifies argument number"),
> + int_fixed(ArgNum), suffix(","),
> + words("but argument numbers start at 1."), nl],
> + Spec = simplest_spec($pred, severity_error, phase_parse_tree_to_hlds,
> + Context, Pieces).
> +
> +:- func format_call_error_wrong_type(pred_info, prog_context,
> + int, int, int, string, string, string) = error_spec.
> +
...
> diff --git a/compiler/format_call.m b/compiler/format_call.m
> index f23869245..9e1c7ec45 100644
> --- a/compiler/format_call.m
> +++ b/compiler/format_call.m
> @@ -12,16 +12,26 @@
> %
> % This module has two related jobs.
> %
> -% - The first job is to generate warnings about calls to string.format,
> -% io.format and stream.string_writer.format in which the format string
> -% and the supplied lists of values do not agree.
> +% - The first job is to check calls to
> +%
> +% - the Mercury library predicates and functions string.format,
> +% io.format and stream.string_writer.format, and
> +%
> +% - any user-written predicates or functions which have a format_call pragma
> +%
> +% to see whether the format string and the supplied lists of values agree,
> +% and to generate warnings for calls in which they do not.
> %
> % The difficult part of this job is actually finding the values of the
> % variables representing the format string and the list of values to be
> % printed.
> %
> -% - The second job is to try to transform well formed calls into code
> +% - The second job is to try to transform well formed calls to
> +% string.format, io.format and stream.string_writer.format into code
> % that interprets the format string at compile time, rather than runtime.
> +% (We cannot do the same for user-written predicates or functions with
> +% format_call pragmas, because we don't know what callee intends to do
what *the* callee
> +% with the checked arguments.)
> %
> % Our general approach to the first job is a backwards traversal of the
> % procedure body. During this traversal, we assign an id to every conjunction
...
> diff --git a/compiler/hlds_pred.m b/compiler/hlds_pred.m
> index cb59365a5..e2a3706a9 100644
> --- a/compiler/hlds_pred.m
> +++ b/compiler/hlds_pred.m
...
> @@ -701,6 +719,8 @@
> :- pred pred_info_set_obsolete_in_favour_of(
> maybe(list(sym_name_arity))::in,
> pred_info::in, pred_info::out) is det.
> +:- pred pred_info_set_format_call(maybe(format_call)::in,
> + pred_info::in, pred_info::out) is det.
> :- pred pred_info_set_instance_method_arg_types(list(mer_type)::in,
> pred_info::in, pred_info::out) is det.
> :- pred pred_info_set_clauses_info(clauses_info::in,
> @@ -1145,6 +1165,32 @@ marker_name(marker_fact_table_semantic_errors, "fact_table_semantic_errors").
> % as obsolete, this will be "no".
> psi_obsolete_in_favour_of :: maybe(list(sym_name_arity)),
>
> + % If this field contain yes(FormatCall), then this predicate
s/contain/contains/
> + % has a format_call pragma, and FormatCall contains both the
> + % <format string, values list> argument number pairs
> + % specified in that pragma, and the context of that pragma.
> + % If this field contains no, then the predicate does not have
> + % a format_call pragma.
...
> diff --git a/compiler/prog_data_pragma.m b/compiler/prog_data_pragma.m
> index 49eb41211..cfb354d15 100644
> --- a/compiler/prog_data_pragma.m
> +++ b/compiler/prog_data_pragma.m
> @@ -435,6 +435,39 @@ tabled_eval_method_to_table_type(EvalMethod) = TableTypeStr :-
> % (in the case of user-defined equality or comparison) or
> % propagating an exception from them.
>
> +%---------------------------------------------------------------------------%
> +%
> +% Stuff for the `format_call' pragma.
> +%
> +
> +:- interface.
> +
> +:- type format_string_values
> + ---> format_string_values(
> + % The format_call pragma allows uses to specify pairs of
s/uses/users/
...
> diff --git a/tests/warnings/format_call_warning.m b/tests/warnings/format_call_warning.m
> index e69de29bb..8f52b5666 100644
> --- a/tests/warnings/format_call_warning.m
> +++ b/tests/warnings/format_call_warning.m
> @@ -0,0 +1,68 @@
> +%---------------------------------------------------------------------------%
> +% vim: ts=4 sw=4 et ft=mercury
> +%---------------------------------------------------------------------------%
> +
> +:- module format_call_warning.
> +:- interface.
> +
> +:- import_module io.
> +
> +:- pred main(io::di, io::uo) is det.
> +
> +%---------------------------------------------------------------------------%
> +%---------------------------------------------------------------------------%
> +
> +:- implementation.
> +
> +:- import_module int.
> +:- import_module list.
> +:- import_module string.
> +
> +main(!IO) :-
> + CurLevel = 6,
> + Msg = "The record temperature is",
> + TempA = 42.3,
> + TempB = 43.2,
> +
> + % This call should get warnings for all four fs/vl pairs.
> + maybe_log_msg(CurLevel, 1,
> + "%%s: 5.2f", [f(TempA)], [f(TempB)],
> + "%5.2f", [s(Msg), f(TempA)], [s(Msg), f(TempB)], !IO),
> +
> + % This call should get a warning for only the second fs/vl pair.
> + maybe_log_msg(CurLevel, 1,
> + "%5.2f", [f(TempA)], [f(TempB), s(Msg)],
> + "%s: %5.2f", [s(Msg), f(TempA)], [s(Msg), f(TempB)], !IO),
> +
> + % This call should not get any warnings.
> + maybe_log_msg(CurLevel, 1,
> + "%5.2f", [f(TempA)], [f(TempB)],
> + "%s: %5.2f", [s(Msg), f(TempA)], [s(Msg), f(TempB)], !IO).
> +
> +%---------------------------------------------------------------------------%
> +
> +:- pred maybe_log_msg(int::in, int::in,
> + string::in, list(poly_type)::in, list(poly_type)::in,
> + string::in, list(poly_type)::in, list(poly_type)::in,
> + io::di, io::uo) is det.
> +:- pragma format_call(pred(maybe_log_msg/10),
> + [format_string_values(3, 4), format_string_values(3, 5),
> + format_string_values(6, 7), format_string_values(6, 8)]).
> +
> +maybe_log_msg(CurLogLevel, MsgLevel,
> + FormatStrA, ValuesListA1, ValuesListA2,
> + FormatStrB, ValuesListB1, ValuesListB2, !IO) :-
> + Diff = MsgLevel - CurLogLevel,
> + ( if Diff >= 15 then
> + io.format(FormatStrA, ValuesListA1, !IO)
> + else if Diff >= 10 then
> + io.format(FormatStrA, ValuesListA2, !IO)
> + else if Diff >= 5 then
> + io.format(FormatStrB, ValuesListB1, !IO)
> + else if Diff >= 0 then
> + io.format(FormatStrB, ValuesListB2, !IO)
> + else
> + io.format("%d", ValuesListB2, !IO)
Add a comment saying that this last call is intended to generate a warning
about there begin unknown format values in the call to io.format.
The testing here is not comprehensive enough. We should also have:
1. A test of the case where the new pragam has a single format_string_values
argument (i.e. the common case), since the error message has a different form
in that case.
2. A multi-module test case that checks that calls to imported predicates that
are the subject of format_call pragmas have the checks applied.
3. A format_call pragma applied to a multi-moded predicate. (I will add
such a test after this is committed, since I can extract something from G12
for it.)
The diff looks fine otherwise.
Julien.
More information about the reviews
mailing list