[m-rev.] for review: warn about unneeded initial and final state vars

Peter Wang novalazy at gmail.com
Thu May 22 14:19:25 AEST 2025


On Thu, 22 May 2025 12:21:48 +1000 "Zoltan Somogyi" <zoltan.somogyi at runbox.com> wrote:
> Refine unused final statevar warnings.
> 

> diff --git a/compiler/pre_typecheck.m b/compiler/pre_typecheck.m
> index f36558037..ce43cb464 100644
> --- a/compiler/pre_typecheck.m
> +++ b/compiler/pre_typecheck.m
> @@ -423,64 +436,224 @@ warn_about_any_unneeded_initial_statevars(PredInfo, HeadVars,
>  
>  %---------------------%
>  
> +:- pred maybe_warn_about_unneeded_final_statevar(module_info::in,
> +    pred_info::in, set(string)::in, prog_context::in, list(clause)::in,
> +    uint::in, init_and_final::in,
> +    list(error_spec)::in, list(error_spec)::out) is det.
> +
> +maybe_warn_about_unneeded_final_statevar(ModuleInfo, PredInfo,
> +        BodyVarSVarNameSet, HeadClauseContext, TailClauses,
> +        InitArgNum, InitAndFinal, !Specs) :-
> +    InitAndFinal = init_and_final(FinalArgNum, SVarNameSet),
> +    set.intersect(BodyVarSVarNameSet, SVarNameSet, SVarNameSetInBody),
> +    ( if set.is_non_empty(SVarNameSetInBody) then
> +        % The body of at least one clause in the definition of this predicate
> +        % (as opposed to the clause heads) contains a reference to an initial
> +        % or to a final version of this state var. We should therefore report
> +        % that only the *final* statevar argument is unneeded.
> +        %
> +        % There is one exception to this. For state variables that represent
> +        % unique state, accessing the initial version *and preserving
> +        % uniqueness in a way that is visible to the caller* requires
> +        % returning a new version of the state var, so that its mode
> +        % can tell the caller that the returned reference is still unique.
> +        ( if
> +            pred_args_are_free_of_declared_uniqueness(ModuleInfo, PredInfo,
> +                InitArgNum, FinalArgNum)
> +        then
> +            warn_about_unneeded_final_statevar(PredInfo, HeadClauseContext,
> +                TailClauses, FinalArgNum, SVarNameSet, !Specs)
> +        else
> +            true
> +        )
> +    else
> +        % The definition of this predicate is missing not only any middle
> +        % occurrence of this state var, it is also missing any initial
> +        % or final occurrence, except in the clause heads.
> +        % We should therefore report that *both* arguments are unneeded.
> +        %
> +        % Uniqueness concerns do not apply here, because if the programmer
> +        % deletes both arguments, the reference to the unique state that
> +        % the caller would have passed to PredInfo is still available to it.

PredInfo -> to the predicate

> +        warn_about_unneeded_initial_final_statevar(PredInfo, HeadClauseContext,
> +            TailClauses, InitArgNum, FinalArgNum, SVarNameSet, !Specs)
> +    ).
> +
> +%---------------------%
> +
> +:- pred gather_clause_body_vars(clause::in,
> +    set_of_progvar::in, set_of_progvar::out) is det.

I suggest renaming this to gather_clause_body_non_svar_copy_vars
or similar.

> +
> +gather_clause_body_vars(Clause, !BodyVars) :-
> +    BodyGoal = Clause ^ clause_body,
> +    % Note that even for clauses that are facts in the source code,
> +    % such as "noop(!IO).", will contain a nonempty body goal, since
> +    % the conversion to HLDS will create a clause of the form
> +    %
> +    %   noop(STATE_VARIABLE_IO_0, STATE_VARIABLE_IO) :-
> +    %       STATE_VARIABLE_IO = STATE_VARIABLE_IO_0.
> +    %
> +    % What our caller wants to know, for a given state var, is not
> +    % "do progvars representing versions of this state var occur in the
> +    % body goal of the clause", because we know in advance that for any
> +    % state var that occurs in the clause head, the answer must be "yes".
> +    % What it wants to know instead is "do progvars representing versions
> +    % of this state var occur in the body goal of the clause *outside of"
> +    % unify goals that just copy state var values".

I suggest:

"body goal of the clause" -> "clause body"

"outside of" -> "other than"

> +    %
> +    % Here we gather the data that allows our caller to answer that question
> +    % for any state var.
> +    non_svar_copy_goal_vars(BodyGoal, BodyGoalVars),
> +    set_of_var.union(BodyGoalVars, !BodyVars).
> +

> diff --git a/compiler/state_var.m b/compiler/state_var.m
> index 8527a4de6..b113f56eb 100644
> --- a/compiler/state_var.m
> +++ b/compiler/state_var.m
> @@ -605,6 +614,57 @@ new_state_var_instance(StateVar, NameSource, Var, !UrInfo) :-
>  initial_state_var_name(SVarName) = ProgVarName :-
>      ProgVarName = string.format("STATE_VARIABLE_%s_0", [s(SVarName)]).
>  
> +is_prog_var_for_some_state_var(VarSet, Var, SVarName) :-
> +    % All prog_vars representing state vars are named.
> +    varset.search_name(VarSet, Var, VarName),
> +    string.remove_prefix("STATE_VARIABLE_", VarName, AfterStdPrefix),
> +    UnderscoreSeparatedPieces = string.split_at_char('_', AfterStdPrefix),
> +    (
> +        UnderscoreSeparatedPieces = [],
> +        % There is no underscore, so there can be no numerical suffix.
> +        SVarName = AfterStdPrefix
> +    ;
> +        UnderscoreSeparatedPieces = [_ | _],
> +        list.det_last(UnderscoreSeparatedPieces, LastPiece),
> +        string.to_int(LastPiece, _N),
> +        % This is either the initial or a middle version of SVarName.
> +        % (Initial if _N is zero, and middle otherwise)
> +        NumericalSuffix = "_" ++ LastPiece,
> +        SVarName = string.det_remove_suffix(AfterStdPrefix, NumericalSuffix)
> +    ).

You could use string.find_last_char and string.unsafe_between.

> diff --git a/tests/warnings/unneeded_final_statevar.m b/tests/warnings/unneeded_final_statevar.m
> index e69de29bb..6e24ec7c4 100644
> --- a/tests/warnings/unneeded_final_statevar.m
> +++ b/tests/warnings/unneeded_final_statevar.m
> @@ -0,0 +1,44 @@
> +%---------------------------------------------------------------------------%
> +% vim: ts=4 sw=4 et ft=mercury
> +%---------------------------------------------------------------------------%
> +
> +:- module unneeded_final_statevar.
> +:- interface.
> +:- import_module io.
> +
> +:- pred p1(io::di, io::uo) is det.
> +:- pred p2(int::mdi, int::muo) is semidet.
> +:- pred p3(int::mdi, int::muo) is semidet.
> +
> +%---------------------------------------------------------------------------%
> +
> +:- implementation.
> +:- import_module int.
> +
> +%---------------------------------------------------------------------------%
> +
> +% p1 should not get a warning, due to the heuristic rule that says
> +% "do not generate unused final statevar warnings for a predicate
> +% if it is defineing clauses are all facts".

defining

I have tested the patch, and it appears to work well. Thanks.

Peter


More information about the reviews mailing list