[m-rev.] for post-commit review: more specific warnings about unresolved polymorphism

Julien Fischer jfischer at opturion.com
Mon May 5 15:03:20 AEST 2025


On Sat, 3 May 2025 at 17:46, Zoltan Somogyi <zoltan.somogyi at runbox.com> wrote:
>
> Make a warning more specific.

...

> diff --git a/compiler/var_origins.m b/compiler/var_origins.m
> new file mode 100644
> index 000000000..1540fc3ba
> --- /dev/null
> +++ b/compiler/var_origins.m
> @@ -0,0 +1,809 @@
> +%---------------------------------------------------------------------------%
> +% vim: ft=mercury ts=4 sw=4 et
> +%---------------------------------------------------------------------------%
> +% Copyright (C) 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.
> +%---------------------------------------------------------------------------%
> +%
> +% This module provides mechanisms to
> +%
> +% - map each variable in a predicate to its earliest occurrence in each
> +%   clause of a given predicate (its origins), and
> +%
> +% - to map descriptions of such origins into text that can be included
> +%   in error messages.
> +%
> +% The overall intention is to improve the readability of error messages
> +% such as
> +%
> +%   Warning: unresolved polymorphism in predicate `maybe_throw_pred'/2.
> +%   The variable with an unbound type was:
> +%       `V_7': exception.exception_result(T).
> +%   The unbound type variable `T' will be implicitly bound to
> +%   the builtin type `void'.
> +%
> +% Each of these lines has traditionally been prefixed by the context
> +% of the predicate as a whole. When the subject of the diagnostic is
> +% a named variable, this does not matter, since users can look for
> +% occurrences of that variable in the predicate's code. However, for
> +% unnamed variables, they cannot. This is why this module provides
> +% the mechanisms needed to extend the diagnostic with text such as
> +%
> +%   `V_7' represents the term `exception(Exception)'.
> +%
> +% with the preceding context pinpointing the line where the unnamed
> +% variable first occurs. See tests/warnings/unresolved_polymorphism_anon.m.
> +%
> +% Because this intention is to help with error messages created during
> +% type analysis, this module does not look at, or use, any part of the HLDS
> +% that is meaningful during type analysis, such as instmap_deltas.

I think you mean mode analysis there.

...

> +:- type var_origin
> +    --->    var_origin_clause_head(
> +                % The variable's first occurrence is in the clause head.
> +                voch_context    :: prog_context,
> +
> +                % The number of the clause in the predicate's definition.
> +                % The first clause is clause #1.
> +                voch_clause     :: uint,
> +
> +                % The position of the variable in the clause head.
> +                % The first argument is argument #1.
> +                voch_arg_num    :: uint
> +            )
> +    ;       var_origin_lambda_head(
> +                % The variable's first occurrence is in the head of
> +                % a lambda expression.
> +                volh_context    :: prog_context,
> +                volh_p_or_f     :: pred_or_func,
> +                volh_arg_num    :: uint
> +            )
> +    ;       var_origin_unify_var(
> +                vouv_context    :: prog_context,
> +                % The other variable that the variable mapped to this origin
> +                % is unified with.
> +                vouv_lorhs      :: lhs_or_rhs
> +            )
> +    ;       var_origin_unify_func(
> +                vouf_context    :: prog_context,
> +
> +                % The cons_id in the unification.
> +                vouf_cons_id    :: cons_id,
> +
> +                % If the variable mapped to this origin is the LHS var,
> +                % then this lists the RHS vars.
> +                % If the variable mapped to this origin is one of the RHS vars,
> +                % then this gives its position on the RHS, as well as
> +                % the LHS var.
> +                vouf_lorhsa     :: lhs_or_rhs_arg
> +            )
> +    ;       var_origin_plain_call(
> +                vopc_context    :: prog_context,
> +                % The id of the callee, and the number of the argument
> +                % position that is occupied by the variable mapped
> +                % to this origin. (If that variable is only a *component*
> +                % of the argument, and not the whole argument, then its
> +                % origin will be unification introduced by the code of
> +                % superhomoneous.m.)
> +                vopc_callee     :: pred_id,
> +                vopc_arg_num    :: uint
> +            )
> +    ;       var_origin_foreign_call(
> +                vofc_context    :: prog_context,
> +                % We treat foreign language calls the same way as plain calls.
> +                vofc_callee     :: pred_id,
> +                vofc_arg_num    :: uint
> +            )
> +    ;       var_origin_generic_call(
> +                vogc_context    :: prog_context,
> +                % We treat treat language calls similarly to plain calls,
> +                % just with a different specification of the callee.
> +                vogc_callee     :: generic_call,
> +                vogc_arg_num    :: uint
> +            ).
> +
> +:- type lhs_or_rhs
> +    --->    lor_lhs(
> +                    rhs_var     :: prog_var
> +            )
> +    ;       lor_rhs(
> +                    lhs_var     :: prog_var
> +            ).
> +
> +:- type lhs_or_rhs_arg
> +    --->    lora_lhs(
> +                rhs_argvars     :: list(prog_var)
> +            )
> +    ;       lora_rhs(
> +                lhs_var         :: prog_var,
> +                rhs_arg_num     :: uint
> +            ).
> +
> +    % Values of this type map each variable to
> +    %
> +    % - its origin, if it has only one origin (that we have seen so far),
> +    %
> +    % - but if the variable has its origin in a (possibly nested) branched
> +    %   control structure, it will contain its origin on each branch.
> +    %   Note that multiple clauses, being equivalent to multiple disjuncts,
> +    %   count as branches of a branched control structure.
> +:- type var_origins_map == map(prog_var, set(var_origin)).
> +
> +    % compute_var_origins_in_pred(CollectPred, PredInfo0, OriginsMap, !Acc):
> +    %
> +    % Given a predicate, construct and return OriginsMap, which will map
> +    % each variable in its clauses to its set of origins.
> +    %
> +    % Note that one kind of goal, negations, introduce scopes that 'forget'
> +    % origins. This is because if a variable is generated in a negated goal,
> +    % it cannot be referred to from outside the negation. Therefore any
> +    % additions to OriginsMap inside a negated goal will be effectivel

s/effectivel/effectively/

> +    % forgotten when the traversal leaves the negation.
> +    %
> +    % This is why instead of letting our caller post-process OriginsMap,
> +    % we allow it to process each addition to OriginsMap as it is made.
> +    % The traversal will call CollectPred after each such addition,
> +    % passing it both the already-updated OriginsMap, and the pair of
> +    % <variable, origin> that was just added.
> +    %
> +:- pred compute_var_origins_in_pred(
> +    record_var_origin(T)::in(record_var_origin),
> +    pred_info::in, var_origins_map::out, T::in, T::out) is det.

...

> +:- pred update_var_origin(record_var_origin(T)::in(record_var_origin),
> +    prog_var::in, var_origin::in,
> +    var_origins_map::in, var_origins_map::out, T::in, T::out) is det.
> +
> +update_var_origin(CollectPred, Var, VarOrigin, !OriginsMap, !Acc) :-
> +    ( if map.search(!.OriginsMap, Var, _VarOriginsCord0) then
> +        % set.insert(VarOrigin, VarOriginsCord0, VarOriginsCord),
> +        % map.det_update(Var, VarOriginsCord, !OriginsMap)
> +        % ZZZ


What's the ZZZ for?


> +        true
> +    else
> +        VarOriginsCord = set.make_singleton_set(VarOrigin),
> +        map.det_insert(Var, VarOriginsCord, !OriginsMap),
> +        CollectPred(!.OriginsMap, Var, VarOrigin, !Acc)
> +    ).

...

> diff --git a/tests/warnings/unresolved_polymorphism_anon.m b/tests/warnings/unresolved_polymorphism_anon.m
> new file mode 100644
> index 000000000..a45bf4ff7
> --- /dev/null
> +++ b/tests/warnings/unresolved_polymorphism_anon.m
> @@ -0,0 +1,68 @@
> +%---------------------------------------------------------------------------%
> +% vim: ts=4 sw=4 et ft=mercury
> +%---------------------------------------------------------------------------%
> +%
> +% When the compiler generates a warning about unresolved polymorphism,
> +% sometimes the variable being complained about is an anonymous variable.
> +% Since by definition users cannot see such variables in their programs,
> +% the error message does not give them any guidance of what they can change
> +% to make the warning go away.
> +%
> +% This test case tests the operation of the code in the compiler
> +% (var_origins.m) that generates messages to tell users
> +%
> +% - both the context of the first occurrence of the anonymous variable
> +%   as a filename/linenumber pair,
> +%
> +% - and how the variable fits in with the code in which it occurs,
> +%   which should help focus their attention on the affected *part* of that
> +%   line.
> +%
> +% This test case is a cut-down form of the code that motivated the change
> +% being tested here: an unresolved polymorphism in extras/odbc/odbc.m.
> +%
> +% XXX It would be nice to test variable origins other than just "this variable
> +% represents a term constructed here". We can of coyrse add test cases

s/coyrse/course/

> +% for such situations as we come across them.

The rest looks fine.

Julien.


More information about the reviews mailing list