Files
mercury/compiler/check_pragma_format_call.m
Zoltan Somogyi 307b1dc148 Split up error_util.m into five modules.
compiler/error_spec.m:
    This new module contains the part of the old error_util.m that defines
    the error_spec type, and some functions that can help construct pieces
    of error_specs. Most modules of the compiler that deal with errors
    will need to import only this part of the old error_util.m.

    This change also renames the format_component type to format_piece,
    which matches our long-standing naming convention for variables containing
    (lists of) values of this type.

compiler/write_error_spec.m:
    This new module contains the part of the old error_util.m that
    writes out error specs, and converts them to strings.

    This diff marks as obsolete the versions of predicates that
    write out error specs to the current output stream, without
    *explicitly* specifying the intended stream.

compiler/error_sort.m:
    This new module contains the part of the old error_util.m that
    sorts lists of error specs and error msgs.

compiler/error_type_util.m:
    This new module contains the part of the old error_util.m that
    convert types to format_pieces that generate readable output.

compiler/parse_tree.m:
compiler/notes/compiler_design.html:
    Include and document the new modules.

compiler/error_util.m:
    The code remaining in the original error_util.m consists of
    general utility predicates and functions that don't fit into
    any of the modules above.

    Delete an unneeded pair of I/O states from the argument list
    of a predicate.

compiler/file_util.m:
    Move the unable_to_open_file predicate here from error_util.m,
    since it belongs here. Mark another predicate that writes
    to the current output stream as obsolete.

compiler/hlds_error_util.m:
    Mark two predicates that wrote out error_spec to the current output
    stream as obsolete, and add versions that take an explicit output stream.

compiler/Mercury.options:
    Compile the modules that call the newly obsoleted predicates
    with --no-warn-obsolete, for the time being.

compiler/*.m:
    Conform to the changes above, mostly by updating import_module
    declarations, and renaming format_component to format_piece.
2022-10-12 20:50:16 +11:00

356 lines
16 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2022 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% This module checks the validity of format_call pragmas for predicates
% in the module being compiled.
%
% Each format_call pragma specifies one or more pairs of argument positions,
% with each pair specifying the position of a format string argument
% and a values list argument. We check whether the arguments in the
% nominated positions
%
% - actually exist,
% - have the expected types, string and list(poly_type) respectively,
% - and are inputs.
%
% In the presence of type and/or mode inference, the second and third checks
% can be done only after type and mode analysis. And the checks must be done
% before the simplification pass at the end of semantic analysis, because
% that is when format_call.m checks calls to predicates with these pragmas,
% using code that relies on them being correct. The code of this module
% is therefore invoked in the interval in between (provided that there are
% any pragmas to check, which usually there won't be).
%
% If any argument fails the checks above, then, besides generating an error
% message for each failure, we delete the argument pair it is a part of.
% In the usual case of a pragma that consists of one argument pair, this
% effectively deletes the pragma. The point is that any argument pairs
% in any pragmas left after this pass will have passed the above checks.
%
%---------------------------------------------------------------------------%
:- module check_hlds.check_pragma_format_call.
:- interface.
:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module hlds.hlds_pred.
:- import_module parse_tree.
:- import_module parse_tree.error_spec.
:- import_module list.
:- import_module set.
%---------------------------------------------------------------------------%
:- pred check_pragma_format_call_preds(set(pred_id)::in,
module_info::in, module_info::out,
list(error_spec)::in, list(error_spec)::out) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.mode_test.
:- import_module hlds.hlds_error_util.
:- import_module hlds.status.
:- import_module mdbcomp.
:- import_module mdbcomp.builtin_modules.
:- import_module mdbcomp.prim_data.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.builtin_lib_types.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_data_pragma.
:- import_module bool.
:- import_module int.
:- import_module map.
:- import_module maybe.
:- import_module one_or_more.
:- import_module pair.
:- import_module require.
%---------------------------------------------------------------------------%
check_pragma_format_call_preds(FormatCallPredIds, !ModuleInfo, !Specs) :-
list.foldl2(check_pragma_format_call_pred,
set.to_sorted_list(FormatCallPredIds), !ModuleInfo, !Specs).
:- pred check_pragma_format_call_pred(pred_id::in,
module_info::in, module_info::out,
list(error_spec)::in, list(error_spec)::out) is det.
check_pragma_format_call_pred(PredId, !ModuleInfo, !Specs) :-
module_info_pred_info(!.ModuleInfo, PredId, PredInfo0),
pred_info_get_format_call(PredInfo0, MaybeFormatCall0),
(
MaybeFormatCall0 = no,
% add_pragma.m should add a pred_id to the set of predicates
% that have a format_call pragma if and only if it also sets
% this field to yes(...).
unexpected($pred, "MaybeFormatCall0 = no")
;
MaybeFormatCall0 = yes(FormatCall0)
),
FormatCall0 = format_call(Context, OoMFormatArgs0),
FormatArgs0 = one_or_more_to_list(OoMFormatArgs0),
list.length(FormatArgs0, NumFormatArgs0),
pred_info_get_proc_table(PredInfo0, ProcTable0),
map.to_sorted_assoc_list(ProcTable0, ProcIdsInfos0),
list.length(ProcIdsInfos0, NumProcs),
% Setting the maximum argument number that a format_call pragma
% may refer to be from the *user* arity means that format_call
% pragmas cannot refer to the return values of functions.
% The reason for this is that a function's return value
% is supposed to be output, while the format string and
% values list arguments are supposed to be inputs.
user_arity(MaxArgNum) = pred_info_user_arity(PredInfo0),
pred_info_get_arg_types(PredInfo0, ArgTypes),
check_format_args(!.ModuleInfo, PredInfo0, Context, MaxArgNum, ArgTypes,
NumProcs, ProcIdsInfos0, NumFormatArgs0,
1, FormatArgs0, FormatArgs, !Specs),
(
FormatArgs = [HeadFormatArgs | TailFormatArgs],
OoMFormatArgs = one_or_more(HeadFormatArgs, TailFormatArgs),
FormatCall = format_call(Context, OoMFormatArgs),
MaybeFormatCall = yes(FormatCall)
;
FormatArgs = [],
MaybeFormatCall = no
),
pred_info_set_format_call(MaybeFormatCall, PredInfo0, PredInfo),
module_info_set_pred_info(PredId, PredInfo, !ModuleInfo).
:- pred check_format_args(module_info::in, pred_info::in, prog_context::in,
int::in, list(mer_type)::in, int::in, list(pair(proc_id, proc_info))::in,
int::in, int::in,
list(format_string_values)::in, list(format_string_values)::out,
list(error_spec)::in, list(error_spec)::out) is det.
check_format_args(_, _, _, _, _, _, _, _, _, [], [], !Specs).
check_format_args(ModuleInfo, PredInfo, Context, MaxArgNum, ArgTypes,
NumProcs, ProcIdsInfos, NumFormatArgs, FormatArgNum,
[HeadFmtStrVals | TailFmtStrVals], OKFmtStrVals, !Specs) :-
check_format_args(ModuleInfo, PredInfo, Context, MaxArgNum, ArgTypes,
NumProcs, ProcIdsInfos, NumFormatArgs, FormatArgNum + 1,
TailFmtStrVals, TailOKFmtStrVals, !Specs),
HeadFmtStrVals = format_string_values(OrigArgNumFS, OrigArgNumVL,
CurArgNumFS, CurArgNumVL),
some [!CurSpecs] (
!:CurSpecs = [],
( if OrigArgNumFS > MaxArgNum then
SpecTooLargeFS = format_call_error_too_large_arg_num(PredInfo,
Context, NumFormatArgs, FormatArgNum, MaxArgNum,
OrigArgNumFS, "first"),
!:CurSpecs = [SpecTooLargeFS | !.CurSpecs]
else if OrigArgNumFS < 1 then
SpecTooSmallFS = format_call_error_too_small_arg_num(PredInfo,
Context, NumFormatArgs, FormatArgNum, OrigArgNumFS, "first"),
!:CurSpecs = [SpecTooSmallFS | !.CurSpecs]
else
list.det_index1(ArgTypes, CurArgNumFS, ArgTypeFS),
( if ArgTypeFS = string_type then
true
else
SpecWrongTypeFS = format_call_error_wrong_type(PredInfo,
Context, NumFormatArgs, FormatArgNum, OrigArgNumFS,
"first", "a format string", "string"),
!:CurSpecs = [SpecWrongTypeFS | !.CurSpecs]
),
list.foldl2(
check_for_non_input_arg_nums_in_proc(ModuleInfo, CurArgNumFS),
ProcIdsInfos, 1, _, [], BadModeNumsFS),
(
BadModeNumsFS = []
;
BadModeNumsFS = [_ | _],
SpecWrongModeFS = format_call_error_wrong_mode(PredInfo,
Context, NumFormatArgs, FormatArgNum, OrigArgNumFS,
"first", "a format string", NumProcs, BadModeNumsFS),
!:CurSpecs = [SpecWrongModeFS | !.CurSpecs]
)
),
( if OrigArgNumVL > MaxArgNum then
SpecTooLargeVL = format_call_error_too_large_arg_num(PredInfo,
Context, NumFormatArgs, FormatArgNum, MaxArgNum,
OrigArgNumVL, "second"),
!:CurSpecs = [SpecTooLargeVL | !.CurSpecs]
else if OrigArgNumVL < 1 then
SpecTooSmallVL = format_call_error_too_small_arg_num(PredInfo,
Context, NumFormatArgs, FormatArgNum, OrigArgNumVL, "second"),
!:CurSpecs = [SpecTooSmallVL | !.CurSpecs]
else
list.det_index1(ArgTypes, CurArgNumVL, ArgTypeVL),
( if
ArgTypeVL = defined_type(ListTypeCtorSymName,
[ListElementType], kind_star),
ListTypeCtorSymName =
qualified(ListTypeCtorModuleName, ListTypeCtorName),
is_std_lib_module_name(ListTypeCtorModuleName, "list"),
ListTypeCtorName = "list",
ListElementType = defined_type(PolyTypeCtorSymName,
[], kind_star),
PolyTypeCtorSymName =
qualified(PolyTypeCtorModuleName, PolyTypeCtorName),
is_std_lib_module_name(PolyTypeCtorModuleName, "string"),
PolyTypeCtorName = "poly_type"
then
true
else
SpecWrongTypeVL = format_call_error_wrong_type(PredInfo,
Context, NumFormatArgs, FormatArgNum, OrigArgNumVL,
"second", "a values list", "list(poly_type)"),
!:CurSpecs = [SpecWrongTypeVL | !.CurSpecs]
),
list.foldl2(
check_for_non_input_arg_nums_in_proc(ModuleInfo, CurArgNumVL),
ProcIdsInfos, 1, _, [], BadModeNumsVL),
(
BadModeNumsVL = []
;
BadModeNumsVL = [_ | _],
SpecWrongModeVL = format_call_error_wrong_mode(PredInfo,
Context, NumFormatArgs, FormatArgNum, OrigArgNumVL,
"second", "a values list", NumProcs, BadModeNumsVL),
!:CurSpecs = [SpecWrongModeVL | !.CurSpecs]
)
),
(
!.CurSpecs = [],
OKFmtStrVals = [HeadFmtStrVals | TailOKFmtStrVals]
;
!.CurSpecs = [_ | _],
pred_info_get_status(PredInfo, PredStatus),
PredIsLocal = pred_status_defined_in_this_module(PredStatus),
(
PredIsLocal = yes,
!:Specs = !.CurSpecs ++ !.Specs
;
PredIsLocal = no
% We *could* report !.CurSpecs, but probably it would add
% only "noise" for the programmer. The problem *will* be
% reported when the module that we imported PredInfo from
% is compiled, and until that module is fixed, the only thing
% the programmer is missing out on is an error message about
% a module that he/she may not be actively working on
% at that moment.
),
OKFmtStrVals = TailOKFmtStrVals
)
).
:- 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.
%---------------------%
:- 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"),
quote("format_string_values"), words("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"),
quote("format_string_values"), words("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.
format_call_error_wrong_type(PredInfo, Context, NumFormatArgs, FormatArgNum,
ArgNum, FirstOrSecond, Role, ExpectedType) = Spec :-
Pieces =
format_call_error_prelude(PredInfo, NumFormatArgs, FormatArgNum) ++
[words("the"), words(FirstOrSecond), words("argument of"),
quote("format_string_values"), words("specifies argument number"),
int_fixed(ArgNum), words("as holding"), words(Role), suffix(","),
words("but the type of that argument is not"),
quote(ExpectedType), suffix("."), nl],
Spec = simplest_spec($pred, severity_error, phase_parse_tree_to_hlds,
Context, Pieces).
:- func format_call_error_wrong_mode(pred_info, prog_context,
int, int, int, string, string, int, list(int)) = error_spec.
format_call_error_wrong_mode(PredInfo, Context, NumFormatArgs, FormatArgNum,
ArgNum, FirstOrSecond, Role, NumProcs, BadModeNums) = Spec :-
Pieces0 =
format_call_error_prelude(PredInfo, NumFormatArgs, FormatArgNum) ++
[words("the"), words(FirstOrSecond), words("argument of"),
quote("format_string_values"), words("specifies argument number"),
int_fixed(ArgNum), words("as holding"), words(Role), suffix(",")],
( if NumProcs = 1, BadModeNums = [_] then
Pieces = Pieces0 ++ [words("but that argument is not input."), nl]
else
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
list.sort(BadModeNums, SortedBadModeNums),
BadModeNumPieceLists =
list.map((func(N) = [nth_fixed(N)]), SortedBadModeNums),
BadModeNumPieces =
component_lists_to_pieces("and", BadModeNumPieceLists),
Pieces = Pieces0 ++ [words("but that argument is not input"),
words("in the")] ++ BadModeNumPieces ++
[words("modes of the"), p_or_f(PredOrFunc), suffix("."), nl]
),
Spec = simplest_spec($pred, severity_error, phase_parse_tree_to_hlds,
Context, Pieces).
%---------------------%
:- func format_call_error_prelude(pred_info, int, int)
= list(format_piece).
format_call_error_prelude(PredInfo, NumFormatArgs, FormatArgNum) = Pieces :-
Pieces0 = [words("Error: in the second argument of"),
pragma_decl("format_call"), words("declaration for")] ++
describe_one_pred_info_name(should_not_module_qualify, PredInfo) ++
[suffix(":"), nl],
( if FormatArgNum = 1, NumFormatArgs = 1 then
Pieces = Pieces0
else
Pieces = Pieces0 ++ [words("in the"), nth_fixed(FormatArgNum),
words("element of the list:"), nl]
).
%---------------------------------------------------------------------------%
:- end_module check_hlds.check_pragma_format_call.
%---------------------------------------------------------------------------%