From 199c1a9cd1f73efe5c0f0e69f8b25409d6267230 Mon Sep 17 00:00:00 2001 From: Zoltan Somogyi Date: Fri, 27 Feb 2026 21:27:09 +1100 Subject: [PATCH] Generate mode-specific unused args warnings if needed. compiler/unused_args_warn_pragma.m: The existing code processed only the first procedure of each predicate, skipping all the later procedures. It had a comment saying that it warns about an unused arg only if it was unused in all modes, but this claim was false. Replace this old code with new code that - gathers the set of unused args in each procedure, recording which ones have mode "unused", - considers all the procedures of a predicate together, and then - generates either a single warning for the predicate as a whole, or separate warnings for each procedure that has unused arguments. We now generate a single warning for the predicate only if all the procedures agree both on which arguments are unused, and on which of those are *marked* by the mode as unused. Of course, most of the time this will be the case simply because most predicates have just one procedure. Stop module qualifying predicate names in the warnings we generate, since we do not generate warnings for imported predicates. Color the unqualified name as the subject of the diagnostic. When reporting unused args, list the arguments with "unused" modes separately from the other arguments. Simplify the interface with our caller in unused_args.m. compiler/unused_args.m: Conform to the simplified interface with unused_args_warn_pragma.m. compiler/hlds_error_util.m: Add a new version of an existing utility function. tests/warnings/unused_args_some_modes.{m,err_exp}: Add a test case for the new capability. tests/warnings/Mmakefile: Enable the new test case. Stop mixing "VAR = VALUE" and "Var += VALUE" definitions of make variables. Give some make variables better names. Move some dependency definitions out of a block of rules. tests/warnings/Mercury.options: Enable --warn-unused-args for the new test case. Delete some accidentally-duplicated entries. tests/warnings/unused_args_test.err_exp: Update the expected output. --- compiler/hlds_error_util.m | 22 +- compiler/unused_args.m | 9 +- compiler/unused_args_warn_pragma.m | 352 +++++++++++++----- tests/warnings/Mercury.options | 4 +- tests/warnings/Mmakefile | 38 +- tests/warnings/unused_args_some_modes.err_exp | 10 + tests/warnings/unused_args_some_modes.m | 53 +++ tests/warnings/unused_args_test.err_exp | 4 +- 8 files changed, 374 insertions(+), 118 deletions(-) create mode 100644 tests/warnings/unused_args_some_modes.err_exp create mode 100644 tests/warnings/unused_args_some_modes.m diff --git a/compiler/hlds_error_util.m b/compiler/hlds_error_util.m index 859ec80e0..bc2e058ea 100644 --- a/compiler/hlds_error_util.m +++ b/compiler/hlds_error_util.m @@ -2,7 +2,7 @@ % vim: ft=mercury ts=4 sw=4 et %---------------------------------------------------------------------------% % Copyright (C) 1997-2007, 2009-2012 The University of Melbourne. -% Copyright (C) 2014-2017, 2019-2025 The Mercury team. +% Copyright (C) 2014-2017, 2019-2026 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. %---------------------------------------------------------------------------% @@ -124,6 +124,17 @@ maybe(color_name), should_module_qualify, list(format_piece), pred_proc_id) = list(format_piece). + % describe_one_proc_name_maybe_argmodes(PredInfo, Lang, MaybeColor, + % Qual, SuffixPieces, ProcId) = Spec: + % + % + % Does the same job as describe_one_proc_name_maybe_argmodes, but + % lets the caller look up the pred_info. + % +:- func describe_one_proc_name_pred_info_maybe_argmodes(pred_info, output_lang, + maybe(color_name), should_module_qualify, list(format_piece), proc_id) + = list(format_piece). + %---------------------------------------------------------------------------% % describe_qual_proc_name(ModuleInfo, PredProcId) = Spec: @@ -424,13 +435,20 @@ describe_several_pred_names(ModuleInfo, MaybeColor, ShouldModuleQualify, describe_one_proc_name_maybe_argmodes(ModuleInfo, Lang, MaybeColor, ShouldModuleQualify, SuffixPieces, PredProcId) = Pieces :- - module_info_pred_proc_info(ModuleInfo, PredProcId, PredInfo, ProcInfo), + PredProcId = proc(PredId, ProcId), + module_info_pred_info(ModuleInfo, PredId, PredInfo), + Pieces = describe_one_proc_name_pred_info_maybe_argmodes(PredInfo, + Lang, MaybeColor, ShouldModuleQualify, SuffixPieces, ProcId). + +describe_one_proc_name_pred_info_maybe_argmodes(PredInfo, Lang, MaybeColor, + ShouldModuleQualify, SuffixPieces, ProcId) = Pieces :- PredOrFunc = pred_info_is_pred_or_func(PredInfo), ModuleName = pred_info_module(PredInfo), PredName = pred_info_name(PredInfo), pred_info_get_proc_table(PredInfo, ProcTable), map.count(ProcTable, NumProcs), ( if NumProcs > 1 then + pred_info_proc_info(PredInfo, ProcId, ProcInfo), pred_info_get_orig_arity(PredInfo, PredFormArity), proc_info_get_argmodes(ProcInfo, ArgModes0), NumExtraArgs = num_extra_args(PredFormArity, ArgModes0), diff --git a/compiler/unused_args.m b/compiler/unused_args.m index 6f44eaa0f..25f7a5a8f 100644 --- a/compiler/unused_args.m +++ b/compiler/unused_args.m @@ -122,10 +122,9 @@ unused_args_process_module(GatherPragmas, RecordAnalysis, % maybe_write_string(VeryVerbose, "% Finished analysis.\n", !IO), map.init(ProcToUnusedArgsMap0), - build_proc_to_unused_args_map(!.ModuleInfo, GlobalVarUsageMap, FixpointPredProcIds, - ProcToUnusedArgsMap0, ProcToUnusedArgsMap), + build_proc_to_unused_args_map(!.ModuleInfo, GlobalVarUsageMap, + FixpointPredProcIds, ProcToUnusedArgsMap0, ProcToUnusedArgsMap), - map.keys(ProcToUnusedArgsMap, PredProcIdsToFix), globals.lookup_bool_option(Globals, warn_unused_args, DoWarnBool), ( DoWarnBool = no, DoWarn = do_not_warn_unused_args ; DoWarnBool = yes, DoWarn = do_warn_unused_args @@ -135,10 +134,8 @@ unused_args_process_module(GatherPragmas, RecordAnalysis, ; GatherPragmas = do_gather_pragma_unused_args ) then - set.init(WarnedPredIds0), gather_warnings_and_pragmas(!.ModuleInfo, ProcToUnusedArgsMap, - DoWarn, GatherPragmas, PredProcIdsToFix, WarnedPredIds0, - [], Specs, set.init, PragmaUnusedArgInfos) + DoWarn, GatherPragmas, Specs, PragmaUnusedArgInfos) else Specs = [], set.init(PragmaUnusedArgInfos) diff --git a/compiler/unused_args_warn_pragma.m b/compiler/unused_args_warn_pragma.m index a5baf4587..0ce6d1c61 100644 --- a/compiler/unused_args_warn_pragma.m +++ b/compiler/unused_args_warn_pragma.m @@ -22,7 +22,6 @@ :- 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 parse_tree.prog_item. @@ -51,17 +50,17 @@ :- pred gather_warnings_and_pragmas(module_info::in, proc_to_unused_args_map::in, maybe_warn_unused_args::in, maybe_gather_pragma_unused_args::in, - list(pred_proc_id)::in, set(pred_id)::in, - list(error_spec)::in, list(error_spec)::out, - set(gen_pragma_unused_args_info)::in, - set(gen_pragma_unused_args_info)::out) is det. + list(error_spec)::out, set(gen_pragma_unused_args_info)::out) is det. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- implementation. +:- import_module hlds.hlds_error_util. :- import_module hlds.hlds_markers. +:- import_module hlds.hlds_pred. +:- import_module hlds.mode_test. :- import_module hlds.pred_name. :- import_module hlds.status. :- import_module libs. @@ -69,50 +68,58 @@ :- import_module mdbcomp. :- import_module mdbcomp.prim_data. :- import_module mdbcomp.sym_name. +:- import_module parse_tree.parse_tree_out_info. :- import_module parse_tree.prog_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 pair. :- import_module require. -:- import_module term. :- import_module term_context. %---------------------------------------------------------------------------% -gather_warnings_and_pragmas(_, _, _, _, [], _, !Specs, !PragmaUnusedArgInfos). gather_warnings_and_pragmas(ModuleInfo, ProcToUnusedArgsMap, - DoWarn, DoPragma, [PredProcId | PredProcIds], !.WarnedPredIds, - !Specs, !PragmaUnusedArgInfos) :- - ( if map.search(ProcToUnusedArgsMap, PredProcId, UnusedArgs) then - PredProcId = proc(PredId, ProcId) , - module_info_pred_info(ModuleInfo, PredId, PredInfo), - ( if may_gather_warning_pragma_for_pred(PredInfo) then - ( - DoWarn = do_not_warn_unused_args - ; - DoWarn = do_warn_unused_args, - maybe_gather_warning(ModuleInfo, PredInfo, PredId, ProcId, - UnusedArgs, !WarnedPredIds, !Specs) - ), - ( - DoPragma = do_not_gather_pragma_unused_args - ; - DoPragma = do_gather_pragma_unused_args, - maybe_gather_unused_args_pragma(PredInfo, ProcId, UnusedArgs, - !PragmaUnusedArgInfos) - ) - else - true + DoWarn, DoPragma, Specs, PragmaUnusedArgInfos) :- + map.foldl2(gather_warnings_and_pragmas_ppid(ModuleInfo, DoWarn, DoPragma), + ProcToUnusedArgsMap, + map.init, WarnUnusedPredArgsMap, set.init, PragmaUnusedArgInfos), + map.foldl(warn_unused_args_in_pred, WarnUnusedPredArgsMap, [], Specs). + +:- pred gather_warnings_and_pragmas_ppid(module_info::in, + maybe_warn_unused_args::in, maybe_gather_pragma_unused_args::in, + pred_proc_id::in, list(int)::in, + warn_unused_pred_args_map::in, warn_unused_pred_args_map::out, + set(gen_pragma_unused_args_info)::in, + set(gen_pragma_unused_args_info)::out) is det. + +gather_warnings_and_pragmas_ppid(ModuleInfo, DoWarn, DoPragma, + PredProcId, UnusedArgs, + !WarnUnusedPredArgsMap, !PragmaUnusedArgInfos) :- + PredProcId = proc(PredId, ProcId) , + module_info_pred_info(ModuleInfo, PredId, PredInfo), + ( if may_gather_warning_pragma_for_pred(PredInfo) then + ( + DoWarn = do_not_warn_unused_args + ; + DoWarn = do_warn_unused_args, + maybe_add_proc_to_unused_args_map(ModuleInfo, PredInfo, PredId, + ProcId, UnusedArgs, !WarnUnusedPredArgsMap) + ), + ( + DoPragma = do_not_gather_pragma_unused_args + ; + DoPragma = do_gather_pragma_unused_args, + maybe_gather_unused_args_pragma(PredInfo, ProcId, UnusedArgs, + !PragmaUnusedArgInfos) ) else true - ), - gather_warnings_and_pragmas(ModuleInfo, ProcToUnusedArgsMap, - DoWarn, DoPragma, PredProcIds, !.WarnedPredIds, - !Specs, !PragmaUnusedArgInfos). + ). :- pred may_gather_warning_pragma_for_pred(pred_info::in) is semidet. @@ -260,32 +267,64 @@ may_gather_warning_pragma_for_pred(PredInfo) :- ) ). -:- pred maybe_gather_warning(module_info::in, pred_info::in, - pred_id::in, proc_id::in, list(int)::in, - set(pred_id)::in, set(pred_id)::out, - list(error_spec)::in, list(error_spec)::out) is det. +%---------------------------------------------------------------------------% -maybe_gather_warning(ModuleInfo, PredInfo, PredId, ProcId, UnusedArgs0, - !WarnedPredIds, !Specs) :- - ( if set.member(PredId, !.WarnedPredIds) then - true - else - set.insert(PredId, !WarnedPredIds), - pred_info_get_proc_table(PredInfo, ProcTable), - map.lookup(ProcTable, ProcId, Proc), - pred_info_get_orig_arity(PredInfo, PredFormArity), - proc_info_get_headvars(Proc, HeadVars), - NumExtraArgs = num_extra_args(PredFormArity, HeadVars), - % Strip off the extra type_info/typeclass_info arguments - % inserted at the front by polymorphism.m. - drop_poly_inserted_args(NumExtraArgs, UnusedArgs0, UnusedArgs), - ( - UnusedArgs = [_ | _], - Spec = report_unused_args(ModuleInfo, PredInfo, UnusedArgs), - !:Specs = [Spec | !.Specs] - ; - UnusedArgs = [] +:- type warn_unused_pred_args_map == map(pred_id, warn_unused_pred_args). + +:- type warn_unused_pred_args + ---> warn_unused_pred_args( + pred_info, + one_or_more(pair(proc_id, unused_proc_args)) + ). + +:- type unused_proc_args == one_or_more(unused_proc_arg). +:- type unused_proc_arg + ---> unused_proc_arg( + % The argument number. + int, + + maybe_marked_unused + ). + +:- type maybe_marked_unused + ---> not_marked_unused + ; marked_unused. + +:- pred maybe_add_proc_to_unused_args_map(module_info::in, pred_info::in, + pred_id::in, proc_id::in, list(int)::in, + warn_unused_pred_args_map::in, warn_unused_pred_args_map::out) is det. + +maybe_add_proc_to_unused_args_map(ModuleInfo, PredInfo, PredId, ProcId, + UnusedArgs0, !WarnUnusedPredArgsMap) :- + pred_info_get_orig_arity(PredInfo, PredFormArity), + pred_info_get_arg_types(PredInfo, ArgTypes), + NumExtraArgs = num_extra_args(PredFormArity, ArgTypes), + % Strip off the extra type_info/typeclass_info arguments + % inserted at the front by polymorphism.m. + drop_poly_inserted_args(NumExtraArgs, UnusedArgs0, UnusedArgs), + ( + UnusedArgs = [_ | _], + pred_info_proc_info(PredInfo, ProcId, ProcInfo), + proc_info_get_argmodes(ProcInfo, ArgModes0), + list.det_drop(NumExtraArgs, ArgModes0, ArgModes), + record_which_unused_args_are_marked(ModuleInfo, ArgModes, + UnusedArgs, UnusedProcArgs), + % If UnusedArgs is not empty, then UnusedProcArgs cannot be empty. + det_list_to_one_or_more(UnusedProcArgs, OoMUnusedProcArgs), + ( if + map.search(!.WarnUnusedPredArgsMap, PredId, WarnUnusedPredArgs0) + then + WarnUnusedPredArgs0 = warn_unused_pred_args(_PredInfo, ProcAL0), + one_or_more.cons(ProcId - OoMUnusedProcArgs, ProcAL0, ProcAL), + WarnUnusedPredArgs = warn_unused_pred_args(PredInfo, ProcAL), + map.det_update(PredId, WarnUnusedPredArgs, !WarnUnusedPredArgsMap) + else + ProcAL = one_or_more(ProcId - OoMUnusedProcArgs, []), + WarnUnusedPredArgs = warn_unused_pred_args(PredInfo, ProcAL), + map.det_insert(PredId, WarnUnusedPredArgs, !WarnUnusedPredArgsMap) ) + ; + UnusedArgs = [] ). % Adjust the argument numbers from how they look in an argument list @@ -307,42 +346,175 @@ drop_poly_inserted_args(NumInserted, [HeadArgWith | TailArgsWith], ArgsWithout = [HeadArgWithout | TailArgsWithout] ). - % Warn about unused arguments in a predicate. We consider an argument - % unused *only* if it is unused in *every* mode of the predicate. - % We also never warn about arguments inserted by the polymorphism pass. - % - % The latter test is done by maybe_gather_warning with help from - % drop_poly_inserted_args. - % - % XXX I (zs) would like to know where the first test is done, - % since it is *not* done here. My suspicion is that it is not done at all. - % -:- func report_unused_args(module_info, pred_info, list(int)) = error_spec. +:- pred record_which_unused_args_are_marked(module_info::in, + list(mer_mode)::in, list(int)::in, list(unused_proc_arg)::out) is det. -report_unused_args(_ModuleInfo, PredInfo, UnusedArgs) = Spec :- - list.length(UnusedArgs, NumArgs), - pred_info_get_context(PredInfo, Context), - PredOrFunc = pred_info_is_pred_or_func(PredInfo), - ModuleName = pred_info_module(PredInfo), - PredName = pred_info_name(PredInfo), - pred_info_get_orig_arity(PredInfo, PredFormArity), - user_arity_pred_form_arity(PredOrFunc, - user_arity(UserArityInt), PredFormArity), - SNA = sym_name_arity(qualified(ModuleName, PredName), UserArityInt), - Pieces1 = [words("In"), fixed(pred_or_func_to_full_str(PredOrFunc)), - qual_sym_name_arity(SNA), suffix(":"), nl, words("warning:")], - UnusedArgNs = list.map(func(N) = int_fixed(N), UnusedArgs), - UnusedArgPieces = piece_list_to_color_pieces(color_subject, "and", [], - UnusedArgNs), - ( if NumArgs = 1 then - Pieces2 = [words("argument")] ++ UnusedArgPieces ++ - [words("is")] ++ color_as_incorrect([words("unused.")]) ++ [nl] +record_which_unused_args_are_marked(_, _, [], []). +record_which_unused_args_are_marked(ModuleInfo, ArgModes, + [ArgNum | ArgNums], [ProcArg | ProcArgs]) :- + list.det_index1(ArgModes, ArgNum, ArgMode), + ( if mode_is_unused(ModuleInfo, ArgMode) then + MaybeMarked = marked_unused else - Pieces2 = [words("arguments")] ++ UnusedArgPieces ++ - [words("are")] ++ color_as_incorrect([words("unused.")]) ++ [nl] + MaybeMarked = not_marked_unused ), - Spec = spec($pred, severity_warning(warn_requested_by_option), - phase_code_gen, Context, Pieces1 ++ Pieces2). + ProcArg = unused_proc_arg(ArgNum, MaybeMarked), + record_which_unused_args_are_marked(ModuleInfo, ArgModes, + ArgNums, ProcArgs). + +%---------------------------------------------------------------------------% + +:- pred warn_unused_args_in_pred(pred_id::in, warn_unused_pred_args::in, + list(error_spec)::in, list(error_spec)::out) is det. + +warn_unused_args_in_pred(_PredId, WarnUnusedPredArgs, !Specs) :- + WarnUnusedPredArgs = warn_unused_pred_args(PredInfo, ProcUnusedArgsAL0), + one_or_more.sort(ProcUnusedArgsAL0, ProcUnusedArgsAL), + pred_info_get_proc_table(PredInfo, ProcTable), + one_or_more.foldl2(do_all_procs_have_same_unused_args, ProcUnusedArgsAL, + map.init, UnusedArgsToProcMap, ProcTable, UnmentionedProcTable), + map.to_assoc_list(UnusedArgsToProcMap, UnusedArgsToProcAL), + % We can generate a single warning that applies to the whole predicate + % only if + % - all procedures have some unused arguments, and + % - they all have the *same set* of unused arguments, *and* + % they agree on which unused args are marked as such. + ( if + map.is_empty(UnmentionedProcTable), + UnusedArgsToProcAL = [UnusedProcArgs - _OoMProcIds] + then + report_pred_general_unused_args(PredInfo, UnusedProcArgs, !Specs) + else + % Otherwise, we generate procedure-specific warnings. + % + % We *could* generate a single warning for whole sets of procedures + % that share the same pattern of unused arguments, but + % + % - having different procedures having different sets of unused args + % is extremely rare, and + % + % - if that *does* happen, then getting the mode-specific reports + % in mode number order probably does more to improve readability + % than shortening the output would do, especially if the procedures + % in a set sharing the same set of unused args are not adjacent. + one_or_more.foldl(report_proc_specific_unused_args(PredInfo), + ProcUnusedArgsAL, !Specs) + ). + +:- pred do_all_procs_have_same_unused_args( + pair(proc_id, unused_proc_args)::in, + map(unused_proc_args, one_or_more(proc_id))::in, + map(unused_proc_args, one_or_more(proc_id))::out, + map(proc_id, proc_info)::in, map(proc_id, proc_info)::out) is det. + +do_all_procs_have_same_unused_args(ProcId - ProcUnusedArgs, + !UnusedArgsToProcmap, !UnmentionedProcTable) :- + ( if map.search(!.UnusedArgsToProcmap, ProcUnusedArgs, OoMProcIds0) then + one_or_more.cons(ProcId, OoMProcIds0, OoMProcIds), + map.det_update(ProcUnusedArgs, OoMProcIds, !UnusedArgsToProcmap) + else + OoMProcIds = one_or_more(ProcId, []), + map.det_insert(ProcUnusedArgs, OoMProcIds, !UnusedArgsToProcmap) + ), + % By construction, each ProcId may appear in the input list + % (or actually one_or_more) only once. + map.det_remove(ProcId, _, !UnmentionedProcTable). + + % Warn about unused arguments in a predicate. + % + % We never warn about arguments inserted by the polymorphism pass. + % This filtering out is done by the call to drop_poly_inserted_args + % in maybe_add_proc_to_unused_args_map above. + % +:- pred report_pred_general_unused_args(pred_info::in, unused_proc_args::in, + list(error_spec)::in, list(error_spec)::out) is det. + +report_pred_general_unused_args(PredInfo, ProcUnusedArgs, !Specs) :- + NameColonNlPieces = describe_one_pred_info_name(yes(color_subject), + should_not_module_qualify, [suffix(":"), nl], PredInfo), + pred_info_get_context(PredInfo, Context), + report_unused_args(NameColonNlPieces, Context, ProcUnusedArgs, !Specs). + +:- pred report_proc_specific_unused_args(pred_info::in, + pair(proc_id, unused_proc_args)::in, + list(error_spec)::in, list(error_spec)::out) is det. + +report_proc_specific_unused_args(PredInfo, ProcId - ProcUnusedArgs, !Specs) :- + NameColonNlPieces = describe_one_proc_name_pred_info_maybe_argmodes( + PredInfo, output_mercury, yes(color_subject), + should_not_module_qualify, [suffix(":"), nl], ProcId), + pred_info_proc_info(PredInfo, ProcId, ProcInfo), + proc_info_get_context(ProcInfo, Context), + report_unused_args(NameColonNlPieces, Context, ProcUnusedArgs, !Specs). + +:- pred report_unused_args(list(format_piece)::in, prog_context::in, + unused_proc_args::in, list(error_spec)::in, list(error_spec)::out) is det. + +report_unused_args(NameColonNlPieces, Context, ProcUnusedArgs, !Specs) :- + one_or_more.foldl2(classify_unused_proc_arg, ProcUnusedArgs, + [], UnmarkedArgs0, [], MarkedArgs0), + list.sort(UnmarkedArgs0, UnmarkedArgs), + list.sort(MarkedArgs0, MarkedArgs), + ( + UnmarkedArgs = [] + % Since ProcUnusedArgs cannot be empty, we get here only if + % all the unused arguments are explicitly marked as such. + % In such cases, the warning would be a distraction, not a help. + ; + UnmarkedArgs = [_ | TailUnmarkedArgs], + Pieces1 = [words("In")] ++ NameColonNlPieces ++ [words("warning:")], + UnmarkedArgPieces = piece_list_to_color_pieces(color_subject, + "and", [], UnmarkedArgs), + ( + TailUnmarkedArgs = [], + Pieces2 = [words("argument")] ++ UnmarkedArgPieces ++ + [words("is")] ++ color_as_incorrect([words("unused.")]) ++ [nl] + ; + TailUnmarkedArgs = [_ | _], + Pieces2 = [words("arguments")] ++ UnmarkedArgPieces ++ + [words("are")] ++ color_as_incorrect([words("unused.")]) ++ [nl] + ), + Addendum = marked_unused_args_addendum(MarkedArgs), + Spec = spec($pred, severity_warning(warn_requested_by_option), + phase_code_gen, Context, Pieces1 ++ Pieces2 ++ Addendum), + !:Specs = [Spec | !.Specs] + ). + +:- pred classify_unused_proc_arg(unused_proc_arg::in, + list(format_piece)::in, list(format_piece)::out, + list(format_piece)::in, list(format_piece)::out) is det. + +classify_unused_proc_arg(UnusedProcArg, !UnmarkedArgNums, !MarkedArgNums) :- + UnusedProcArg = unused_proc_arg(ArgNum, MaybeMarked), + ( + MaybeMarked = not_marked_unused, + !:UnmarkedArgNums = [int_fixed(ArgNum) | !.UnmarkedArgNums] + ; + MaybeMarked = marked_unused, + !:MarkedArgNums = [int_fixed(ArgNum) | !.MarkedArgNums] + ). + +:- func marked_unused_args_addendum(list(format_piece)) = list(format_piece). + +marked_unused_args_addendum(MarkedArgs) = Pieces :- + MarkedArgPieces = piece_list_to_color_pieces(color_subject, + "and", [], MarkedArgs), + ( + MarkedArgs = [], + Pieces = [] + ; + MarkedArgs = [_], + Pieces = [words("(Argument")] ++ MarkedArgPieces ++ + [words("is also unused, but its mode")] ++ + color_as_correct([words("marks it")]) ++ + [words("as unused.)"), nl] + ; + MarkedArgs = [_, _ | _], + Pieces = [words("(Arguments")] ++ MarkedArgPieces ++ + [words("are also unused, but their modes")] ++ + color_as_correct([words("mark them")]) ++ + [words("as unused.)"), nl] + ). %---------------------------------------------------------------------------% diff --git a/tests/warnings/Mercury.options b/tests/warnings/Mercury.options index 51b636309..49cdd9d11 100644 --- a/tests/warnings/Mercury.options +++ b/tests/warnings/Mercury.options @@ -31,9 +31,7 @@ MCFLAGS-unused_args_analysis += --warn-unused-args MCFLAGS-unused_args_analysis_helper_1 += --intermodule-analysis MCFLAGS-unused_args_analysis_helper_1 += --optimize-unused-args MCFLAGS-unused_args_analysis_helper_1 += --trace-optimized -MCFLAGS-unused_args_analysis += --intermodule-analysis -MCFLAGS-unused_args_analysis += --optimize-unused-args -MCFLAGS-unused_args_analysis += --warn-unused-args +MCFLAGS-unused_args_some_modes += --warn-unused-args MCFLAGS-unused_args_test += --warn-unused-args MCFLAGS-unused_import += --warn-unused-interface-imports MCFLAGS-inference_test += --infer-all diff --git a/tests/warnings/Mmakefile b/tests/warnings/Mmakefile index 017d569f3..7c5dd1163 100644 --- a/tests/warnings/Mmakefile +++ b/tests/warnings/Mmakefile @@ -8,7 +8,7 @@ MAYBE_J1 = #-----------------------------------------------------------------------------# -ERRORCHECK_PROGS = \ +STD_PROGS = \ abstract_type_decl \ allow_non_contiguity_for \ ambiguous_overloading \ @@ -67,6 +67,7 @@ ERRORCHECK_PROGS = \ unneeded_final_statevar \ unsigned_zero_cmp \ unsorted_import_blocks \ + unused_args_some_modes \ unused_args_test \ unused_import \ unused_interface_import \ @@ -106,20 +107,27 @@ ERRORCHECK_PROGS = \ # # Since we do bootchecks with mmc --make only in non-C grades, # during bootchecks, one or the other of these two tests will prevent us -# from adding unused_args_analysis to ERRORCHECK_PROGS. +# from enabling the unused_args_analysis test case. ifeq ($(MMAKE_USE_MMC_MAKE),yes) -ifeq "$(filter java% csharp%,$(GRADE))" "" - ERRORCHECK_PROGS += unused_args_analysis -endif + ifeq "$(filter java% csharp%,$(GRADE))" "" + C_ONLY_PROGS = unused_args_analysis + else + C_ONLY_PROGS = + endif +else + C_ONLY_PROGS = endif -PROGS = $(ERRORCHECK_PROGS) help_opt_levels help_text up_to_date +# The simple programs are all handled by the same single general rule. +# The other three programs each have their own specific rule. +SIMPLE_PROGS = $(STD_PROGS) $(C_ONLY_PROGS) +PROGS = $(SIMPLE_PROGS) help_opt_levels help_text up_to_date TESTS = $(sort $(PROGS)) include ../Mmake.common -# Module-specific options should go in Mercury.options so they -# can be found by `mmc --make'. +# Module-specific options should go in Mercury.options, +# to allow them to be found by `mmc --make'. include Mercury.options # With `mmc --make', the errors should only go to the `.err' files, not stderr. @@ -127,7 +135,13 @@ MCFLAGS += --output-compile-error-lines 0 MCFLAGS += --color-diagnostics MCFLAGS += --infer-all -$(ERRORCHECK_PROGS:%=%.runtest): %.runtest: %.err_res ; +$(SIMPLE_PROGS:%=%.runtest): %.runtest: %.err_res ; + +# Build the `.analysis' file for unused_args_analysis_helper_1 +# before building unused_args_analysis.c. +# NOTE As mentioned above, these dependencies are only for C. +unused_args_analysis.c: unused_args_analysis_helper_1.c +unused_args_analysis.err: unused_args_analysis_helper_1.c help_opt_levels.runtest: $(MC) $(MCFLAGS) --output-optimization-options-upto 8 \ @@ -142,12 +156,6 @@ help_text.runtest: > help_text.res || \ { cat help_text.res; exit 1; } -# Build the `.analysis' file for unused_args_analysis_helper_1 -# before building unused_args_analysis.c. -# XXX These dependencies are only for C. -unused_args_analysis.c: unused_args_analysis_helper_1.c -unused_args_analysis.err: unused_args_analysis_helper_1.c - # Check that `mmc --make up_to_date.m' generates a warning. up_to_date.runtest: $(MCM) up_to_date.m > up_to_date.err_make 2>&1 diff --git a/tests/warnings/unused_args_some_modes.err_exp b/tests/warnings/unused_args_some_modes.err_exp new file mode 100644 index 000000000..5e1f1ea3d --- /dev/null +++ b/tests/warnings/unused_args_some_modes.err_exp @@ -0,0 +1,10 @@ +unused_args_some_modes.m:024: In `p(in(unused_args_some_modes.i1), in, in, +unused_args_some_modes.m:024: in(free), in(free), out)': +unused_args_some_modes.m:024: warning: arguments 2 and 3 are unused. +unused_args_some_modes.m:024: (Arguments 4 and 5 are also unused, but their +unused_args_some_modes.m:024: modes mark them as unused.) +unused_args_some_modes.m:025: In `p(in(unused_args_some_modes.i2), in, in, +unused_args_some_modes.m:025: in(free), in, out)': +unused_args_some_modes.m:025: warning: arguments 3 and 5 are unused. +unused_args_some_modes.m:025: (Argument 4 is also unused, but its mode marks +unused_args_some_modes.m:025: it as unused.) diff --git a/tests/warnings/unused_args_some_modes.m b/tests/warnings/unused_args_some_modes.m new file mode 100644 index 000000000..f81432082 --- /dev/null +++ b/tests/warnings/unused_args_some_modes.m @@ -0,0 +1,53 @@ +%---------------------------------------------------------------------------% +% vim: ts=4 sw=4 et ft=mercury +%---------------------------------------------------------------------------% +% +% Test how unused_args.m handles arguments whose status (is it used or not, +% and if not, is this expected or not) differs between clauses. +% +%---------------------------------------------------------------------------% + +:- module unused_args_some_modes. +:- interface. +:- import_module char. + +:- type t + ---> f1(int) + ; f2(char). + +:- inst i1 for t/0 + ---> f1(ground). +:- inst i2 for t/0 + ---> f2(ground). + +:- pred p(t, int, int, int, int, int). +:- mode p(in(i1), in, in, unused, unused, out) is det. +:- mode p(in(i2), in, in, unused, in, out) is semidet. + +:- implementation. + +:- pragma promise_equivalent_clauses(pred(p/6)). + +% Input arg I's status in the two clauses is unused/used. +% Input arg J's status in the two clauses is unused/unused. +% Input arg K's status in the two clauses is unused/unused, but both expected. +% Input arg L's status in the two clauses is unused/unused, with ONE expected. + +p(T::in(i1), _I::in, _J::in, _K::unused, _L::unused, O::out) :- + % I is unused in this clause. + % J is unused in this clause. + % K is unused in this clause, but this is expected. + % L is unused in this clause, but this is expected. + T = f1(N), + O = N. +p(T::in(i2), I::in, _J::in, _K::unused, _L::in, O::out) :- + % I is used in this clause. + % J is unused in this clause. + % K is unused in this clause, but this is expected. + % L is unused in this clause. + T = f2(C), + ( if char.is_alpha(C) then + O = I + else + fail + ). diff --git a/tests/warnings/unused_args_test.err_exp b/tests/warnings/unused_args_test.err_exp index bd48bf9db..50a2aefee 100644 --- a/tests/warnings/unused_args_test.err_exp +++ b/tests/warnings/unused_args_test.err_exp @@ -1,4 +1,4 @@ -unused_args_test.m:013: In predicate `unused_args_test.recursive'/3: +unused_args_test.m:013: In predicate `recursive'/3: unused_args_test.m:013: warning: argument 1 is unused. -unused_args_test.m:015: In predicate `unused_args_test.nonrecursive'/1: +unused_args_test.m:015: In predicate `nonrecursive'/1: unused_args_test.m:015: warning: argument 1 is unused.