Files
mercury/library/string.parse_util.m
Zoltan Somogyi 62ec97d443 Report imports shadowed by other imports.
If a module has two or more import_module or use_module declarations
for the same module, (typically, but not always, one being in its interface
and one in its implementation), generate an informational message about
each redundant declaration if --warn-unused-imports is enabled.

compiler/hlds_module.m:
    We used to record the set of imported/used modules, and the set of
    modules imported/used in the interface of the current module. However,
    these sets

    - did not record the distinction between imports and uses;
    - did not allow distinction between single and multiple imports/uses;
    - did not record the locations of the imports/uses.

    The first distinction was needed only by module_qual.m, which *did*
    pay attention to it; the other two were not needed at all.

    To generate messages for imports/uses shadowing other imports/uses,
    we need all three, so change the data structure storing such information
    for *direct* imports to one that records all three of the above kinds
    of information. (For imports made by read-in interface and optimization
    files, the old set of modules approach is fine, and this diff leaves
    the set of thus *indirectly* imported module names alone.)

compiler/unused_imports.m:
    Use the extra information now available to generate a
    severity_informational message about any import or use that is made
    redundant by an earlier, more general import or use.

    Fix two bugs in the code that generated warnings for just plain unused
    modules.

    (1) It did not consider that a use of the builtin type char justified
    an import of char.m, but without that import, the type is not visible.

    (2) It scanned cons_ids in goals in procedure bodies, but did not scan
    cons_ids that have been put into the const_struct_db. (I did not update
    the code here when I added the const_struct_db.)

    Also, add a (hopefully temporary) workaround for a bug in
    make_hlds_passes.m, which is noted below.

    However, there are at least three problems that prevent us from enabling
    --warn-unused-imports by default.

    (1) In some places, the import of a module is used only by clauses for
    a predicate that also has foreign procs. When compiled in a grade that
    selects one of those foreign_procs as the implementation of the predicate,
    the clauses are discarded *without* being added to the HLDS at all.
    This leads unused_imports.m to generate an uncalled-for warning in such
    cases. To fix this, we would need to preserve the Mercury clauses for
    *all* predicates, even those with foreign procs, and do all the semantic
    checks on them before throwing them away. (I tried to do this once, and
    failed, but the task should be easier after the item list change.)

    (2) We have two pieces of code to generate import warnings. The one in
    unused_imports.m operates on the HLDS after type and mode checking,
    while module_qual.m operates on the parse tree before the creation of
    the HLDS. The former is more powerful, since it knows e.g. what types and
    modes are used in the bodies of predicates, and hence can generate warnings
    about an import being unused *anywhere* in a module, as opposed to just
    unused in its interface.

    If --warn-unused-imports is enabled, we will get two separate set of
    reports about an interface import being unused in the interface,
    *unless* we get a type or mode error, in which case unused_imports.m
    won't be invoked. But in case we do get such errors, we don't want to
    throw away the warnings from module_qual.m. We could store them and
    throw them away only after we know we won't need them, or just get
    the two modules to generate identical error_specs for each warning,
    so that the sort_and_remove_dups of the error specs will do the
    throwing away for us for free, if we get that far.

    (3) The valid/bug100.m test case was added as a regression test for a bug
    that was fixed in module_qual.m. However the bug is still present in
    unused_imports.m.

compiler/make_hlds_passes.m:
    Give hlds_module.m the extra information it now needs for each item_avail.

    Add an XXX for a bug that cannot be fixed right now: the setting of
    the status of abstract instances to abstract_imported. (The "abstract"
    part is correct; the "imported" part may not be.)

compiler/intermod.m:
compiler/try_expand.m:
compiler/xml_documentation.m:
    Conform to the change in hlds_module.m.

compiler/module_qual.m:
    Update the documentation of the relationship of this module
    with unused_imports.m.

compiler/hlds_data.m:
    Document a problem with the status of instance definitions.

compiler/hlds_out_module.m:
    Update the code that prints out the module_info to conform to the change
    to hlds_module.m.

    Print status information about instances, which was needed to diagnose
    one of the bugs in unused_imports.m. Format the output for instances
    nicer.

compiler/prog_item.m:
    Add a convenience predicate.

compiler/prog_data.m:
    Remove a type synonym that makes things harder to understand, not easier.

compiler/modules.m:
    Delete an XXX that asks for the feature this diff implements.
    Add another XXX about how that feature could be improved.

compiler/Mercury.options.m:
    Add some more modules to the list of modules on which the compiler
    should be invoked with --no-warn-unused-imports.

compiler/*.m:
library/*.m:
mdbcomp/*.m:
browser/*.m:
deep_profiler/*.m:
mfilterjavac/*.m:
    Delete unneeded imports. Many of these shadow other imports, and some
    are just plain unneeded, as shown by --warn-unused-imports. In a few
    modules, there were a *lot* of unneeded imports, but most had just
    one or two.

    In a few cases, removing an import from a module, because it *itself*
    does not need it, required adding that same import to those of its
    submodules which *do* need it.

    In a few cases, conform to other changes above.

tests/invalid/Mercury.options:
    Test the generation of messages about import shadowing on the existing
    import_in_parent.m test case (although it was also tested very thoroughly
    when giving me the information needed for the deletion of all the
    unneeded imports above).

tests/*/*.{m,*exp}:
    Delete unneeded imports, and update any expected error messages
    to expect the now-smaller line numbers.
2015-08-25 00:38:49 +10:00

350 lines
12 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ts=4 sw=4 et ft=mercury
%---------------------------------------------------------------------------%
% Copyright (C) 2014 The Mercury team.
% This file may only be copied under the terms of the GNU Library General
% Public License - see the file COPYING.LIB in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: string.parse_runtime.m.
%
% This module parses format strings for the runtime system.
% The module string.parse_compiler.m does the same job for the compiler.
% Any changes in one of these modules will probably also require
% a corresponding change in the other.
%
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- module string.parse_util.
:- interface.
:- import_module list.
%---------------------------------------------------------------------------%
:- type string_format_flag_hash
---> flag_hash_clear
; flag_hash_set.
:- type string_format_flag_space
---> flag_space_clear
; flag_space_set.
:- type string_format_flag_zero
---> flag_zero_clear
; flag_zero_set.
:- type string_format_flag_minus
---> flag_minus_clear
; flag_minus_set.
:- type string_format_flag_plus
---> flag_plus_clear
; flag_plus_set.
:- type string_format_flags
---> string_format_flags(
flag_hash :: string_format_flag_hash,
flag_space :: string_format_flag_space,
flag_zero :: string_format_flag_zero,
flag_minus :: string_format_flag_minus,
flag_plus :: string_format_flag_plus
).
:- type string_format_maybe_width
---> no_specified_width
; specified_width(int).
:- type string_format_maybe_prec
---> no_specified_prec
; specified_prec(int).
:- type string_format_int_base
---> base_octal
; base_decimal
; base_hex_lc
; base_hex_uc
; base_hex_p.
:- type string_format_float_kind
---> kind_e_scientific_lc
; kind_e_scientific_uc
; kind_f_plain_lc
; kind_f_plain_uc
; kind_g_flexible_lc
; kind_g_flexible_uc.
:- type poly_kind
---> poly_kind_char
; poly_kind_str
; poly_kind_int
; poly_kind_float.
:- type string_format_error
---> error_no_specifier(
int, % Which specifier were we expecting?
int % How many extra polytypes?
)
; error_unknown_specifier(
int, % Which specifier?
char % The unexpected specifier character.
)
; error_wrong_polytype(
int, % Which specifier?
char, % The specifier character.
poly_kind % The polytype we found.
)
; error_no_polytype(
int, % Which specifier?
char % The specifier character.
)
; error_nonint_star_width(
int, % Which specifier?
poly_kind % The non-i() polytype we found.
)
; error_missing_star_width(
int % Which specifier?
)
; error_nonint_star_prec(
int, % Which specifier?
poly_kind % The non-i() polytype we found.
)
; error_missing_star_prec(
int % Which specifier?
)
; error_extra_polytypes(
int, % Which specifier were we expecting?
int % How many extra polytypes?
).
% Convert an internal report of an error discovered in a format string
% to an error message that can be delivered to users.
%
:- func string_format_error_to_msg(string_format_error) = string.
:- type gather_ended_by
---> found_end_of_string
; found_percent(list(char)). % The list of chars after the percent.
% gather_non_percent_chars(Chars, ManifestChars, EndedBy):
%
% Parse the part of a format string which doesn't contain any conversion
% specifications. The manifest characters in the format string up to
% the next percent sign (if any) are returned in ManifestChars.
% If these were ended by a percent sign, then the characters after the
% percent sign are returning as the argument of found_percent in EndedBy.
%
:- pred gather_non_percent_chars(list(char)::in, list(char)::out,
gather_ended_by::out) is det.
% Record and skip past any flag characters at the start of the char list.
%
:- pred gather_flag_chars(list(char)::in, list(char)::out,
string_format_flags::in, string_format_flags::out) is det.
% get_number_prefix(!Chars, N):
%
% Consume any decimal digits at the start of !.Chars. Return the value
% of the digits found in N, and the left over characters in !:Chars.
% If there are no decimal digits at the start !.Chars, return 0 for N,
% and leave !Chars unchanged.
%
:- pred get_number_prefix(list(char)::in, list(char)::out,
int::out) is det.
% get_nonzero_number_prefix(!Chars, N):
%
% Consume any decimal digits at the start of !.Chars. Return the value
% of the digits found in N, and the left over characters in !:Chars.
% If there are no decimal digits at the start !.Chars, or if the first
% such digit is a zero, fail.
%
:- pred get_nonzero_number_prefix(list(char)::in, list(char)::out,
int::out) is semidet.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module int.
:- import_module maybe.
:- import_module require.
%---------------------------------------------------------------------------%
string_format_error_to_msg(Error) = Msg :-
(
Error = error_no_specifier(SpecNum, NumExtraPolyTypes),
Msg0 = nth_specifier(SpecNum) ++ " is missing",
( if NumExtraPolyTypes = 0 then
Msg = Msg0 ++ ", along with its input."
else if NumExtraPolyTypes = 1 then
Msg = Msg0 ++ "."
else
Msg = Msg0 ++ ", and there are "
++ string.int_to_string(NumExtraPolyTypes - 1)
++ " extra inputs."
)
;
Error = error_unknown_specifier(SpecNum, SpecChar),
Msg = nth_specifier(SpecNum) ++ " uses the unknown "
++ specifier_char(SpecChar) ++ "."
;
Error = error_wrong_polytype(SpecNum, SpecChar, PolyKind),
Msg = nth_specifier(SpecNum) ++ " uses the "
++ specifier_char(SpecChar)
++ ", but the corresponding input is "
++ poly_kind_desc(PolyKind) ++ "."
;
Error = error_no_polytype(SpecNum, SpecChar),
Msg = nth_specifier(SpecNum)
++ ", which uses " ++ specifier_char(SpecChar)
++ ", is missing its input."
;
Error = error_nonint_star_width(SpecNum, PolyKind),
Msg = nth_specifier(SpecNum)
++ " says the width is a runtime input,"
++ " but the next input is " ++ poly_kind_desc(PolyKind)
++ ", not an integer."
;
Error = error_missing_star_width(SpecNum),
Msg = nth_specifier(SpecNum)
++ " says the width is a runtime input,"
++ " but there is no next input."
;
Error = error_nonint_star_prec(SpecNum, PolyKind),
Msg = nth_specifier(SpecNum)
++ " says the precision is a runtime input,"
++ " but the next input is " ++ poly_kind_desc(PolyKind)
++ ", not an integer."
;
Error = error_missing_star_prec(SpecNum),
Msg = nth_specifier(SpecNum)
++ " says the precision is a runtime input,"
++ " but there is no next input."
;
Error = error_extra_polytypes(SpecNum, NumExtraPolyTypes),
( if SpecNum = 1 then
% They aren't extra, since there is no other inputs before them.
Extra = ""
else
Extra = "extra "
),
Msg0 = "There is no " ++ nth(SpecNum) ++ " conversion specifier,",
( if NumExtraPolyTypes = 1 then
Msg = Msg0 ++ " but there is an " ++ Extra ++ "input."
else
Msg = Msg0 ++ " but there are " ++
string.int_to_string(NumExtraPolyTypes) ++
" " ++ Extra ++ "inputs."
)
).
:- func nth_specifier(int) = string.
nth_specifier(SpecNum) =
"The " ++ nth(SpecNum) ++ " conversion specifier".
:- func nth(int) = string.
nth(N) = NStr :-
( if N = 1 then
NStr = "first"
else if N = 2 then
NStr = "second"
else if N = 3 then
NStr = "third"
else if N = 4 then
NStr = "fourth"
else if N = 5 then
NStr = "fifth"
else if N = 6 then
NStr = "sixth"
else if N = 7 then
NStr = "seventh"
else if N = 8 then
NStr = "eighth"
else if N = 9 then
NStr = "ninth"
else if N = 10 then
NStr = "tenth"
else
NStr = string.int_to_string(N) ++ "th"
).
:- func specifier_char(char) = string.
specifier_char(SpecChar) =
"specifier character `" ++ string.char_to_string(SpecChar) ++ "'".
:- func poly_kind_desc(poly_kind) = string.
poly_kind_desc(poly_kind_char) = "a character".
poly_kind_desc(poly_kind_str) = "a string".
poly_kind_desc(poly_kind_int) = "an integer".
poly_kind_desc(poly_kind_float) = "a float".
%---------------------------------------------------------------------------%
gather_non_percent_chars(Chars, NonConversionSpecChars, GatherEndedBy) :-
(
Chars = [HeadChar | TailChars],
( if HeadChar = '%' then
NonConversionSpecChars = [],
% We eat the percent sign.
GatherEndedBy = found_percent(TailChars)
else
gather_non_percent_chars(TailChars, TailNonConversionSpecChars,
GatherEndedBy),
NonConversionSpecChars = [HeadChar | TailNonConversionSpecChars]
)
;
Chars = [],
NonConversionSpecChars = [],
GatherEndedBy = found_end_of_string
).
gather_flag_chars(!Chars, !Flags) :-
% XXX Should we return an error if we find that the format string
% sets the same flag twice?
( if
!.Chars = [Char | !:Chars],
( Char = '#', !Flags ^ flag_hash := flag_hash_set
; Char = ' ', !Flags ^ flag_space := flag_space_set
; Char = '0', !Flags ^ flag_zero := flag_zero_set
; Char = ('-'), !Flags ^ flag_minus := flag_minus_set
; Char = ('+'), !Flags ^ flag_plus := flag_plus_set
)
then
gather_flag_chars(!Chars, !Flags)
else
true
).
get_number_prefix(!Chars, N) :-
get_number_prefix_loop(!Chars, 0, N).
get_nonzero_number_prefix(!Chars, N) :-
!.Chars = [Char | !:Chars],
char.decimal_digit_to_int(Char, CharValue),
Char \= '0',
get_number_prefix_loop(!Chars, CharValue, N).
:- pred get_number_prefix_loop(list(char)::in, list(char)::out,
int::in, int::out) is det.
get_number_prefix_loop(!Chars, N0, N) :-
( if
!.Chars = [Char | !:Chars],
char.decimal_digit_to_int(Char, CharValue)
then
N1 = N0 * 10 + CharValue,
get_number_prefix_loop(!Chars, N1, N)
else
N = N0
).
%---------------------------------------------------------------------------%