[m-rev.] for review: Improve coerce mode error messages.

Peter Wang novalazy at gmail.com
Wed May 12 14:09:08 AEST 2021


compiler/mode_errors.m:
    Replace the two existing options for coerce mode errors with one
    that includes:
    - the path to the input subterm where the error was detected
    - the type of the input subterm
    - the target type that the subterm should be converted to
    - the reason for the error

compiler/modecheck_coerce.m:
    Modify the code such to produce the information above.
    Semidet predicates that previously failed when an error is detected
    are changed to det predicates that return the error information.

    In a couple of places where predicates previously would fail,
    we now abort because the conditions should not occur.

tests/invalid/Mmakefile:
tests/invalid/coerce_mode_error2.err_exp:
tests/invalid/coerce_mode_error2.m:
    Add test case.

tests/invalid/coerce_clobbered.err_exp:
tests/invalid/coerce_instvar.err_exp:
tests/invalid/coerce_int.err_exp:
tests/invalid/coerce_mode_error.err_exp:
tests/invalid/coerce_recursive_inst.err_exp:
tests/invalid/coerce_recursive_type.err_exp:
tests/invalid/coerce_unreachable.err_exp:
    Update expected error messages.

diff --git a/compiler/mode_errors.m b/compiler/mode_errors.m
index 0dd398710..c30df7eb3 100644
--- a/compiler/mode_errors.m
+++ b/compiler/mode_errors.m
@@ -162,12 +162,8 @@
 
     % Mode errors in coerce expressions.
 
-    ;       mode_error_coerce_input_not_ground(prog_var, mer_inst)
-            % The argument in a coerce expression has a non-ground inst.
-
-    ;       mode_error_coerce_ground_invalid(mer_inst, mer_type)
-            % The argument in a coerce expression has a ground inst
-            % that is not valid for the result type.
+    ;       mode_error_coerce_error(coerce_error)
+            % Mode error in coerce expression.
 
     % Mode errors that can happen in more than one kind of goal.
 
@@ -271,6 +267,28 @@
 
 %---------------------%
 
+:- type coerce_error
+    --->    coerce_error(
+                % Path to subterm where the mode error was detected.
+                list(coerce_error_term_path_step),
+                % Type of the subterm.
+                mer_type,
+                % Target type of the conversion.
+                mer_type,
+                coerce_error_reason
+            ).
+
+:- type coerce_error_term_path_step
+    --->    coerce_error_term_path_step(cons_id, int).
+
+:- type coerce_error_reason
+    --->    input_inst_not_ground(mer_inst)
+    ;       invalid_inst_for_input_type(mer_inst)
+    ;       invalid_cons_ids_for_result_type(list(cons_id))
+    ;       has_inst_expect_upcast(mer_inst).
+
+%---------------------%
+
 :- type final_inst_error
     --->    too_instantiated
     ;       not_instantiated_enough
@@ -471,12 +489,8 @@ mode_error_to_spec(ModeInfo, ModeError) = Spec :-
         Spec = mode_error_merge_disj_to_spec(ModeInfo, MergeContext,
             MergeErrors)
     ;
-        ModeError = mode_error_coerce_input_not_ground(Var, VarInst),
-        Spec = mode_error_coerce_input_not_ground_to_spec(ModeInfo, Var,
-            VarInst)
-    ;
-        ModeError = mode_error_coerce_ground_invalid(Inst, Type),
-        Spec = mode_error_coerce_ground_invalid_to_spec(ModeInfo, Inst, Type)
+        ModeError = mode_error_coerce_error(CoerceError),
+        Spec = mode_error_coerce_error_to_spec(ModeInfo, CoerceError)
     ;
         ModeError = mode_error_bind_locked_var(Reason, Var, InstA, InstB),
         Spec = mode_error_bind_locked_var_to_spec(ModeInfo, Reason, Var,
@@ -1308,38 +1322,102 @@ merge_context_to_string(merge_stm_atomic) = "atomic".
 
 %---------------------------------------------------------------------------%
 
-:- func mode_error_coerce_input_not_ground_to_spec(mode_info, prog_var,
-    mer_inst) = error_spec.
+:- func mode_error_coerce_error_to_spec(mode_info, coerce_error) = error_spec.
 
-mode_error_coerce_input_not_ground_to_spec(ModeInfo, Var, VarInst) = Spec :-
+mode_error_coerce_error_to_spec(ModeInfo, Error) = Spec :-
+    Error = coerce_error(TermPath, FromType, ToType, Reason),
     Preamble = mode_info_context_preamble(ModeInfo),
     mode_info_get_context(ModeInfo, Context),
-    mode_info_get_varset(ModeInfo, VarSet),
-    Pieces = [words("mode error:"),
-        quote(mercury_var_to_name_only(VarSet, Var))] ++
-        has_instantiatedness(ModeInfo, VarInst, ",") ++
-        [words("but it must be ground."), nl],
+    varset.init(TypeVarSet),
+    (
+        TermPath = [],
+        TermPathPieces = [],
+        TheTerm = words("the input term")
+    ;
+        TermPath = [_ | _],
+        TermPathPieces =
+            [words("in the input term:"), nl] ++
+            list.condense(list.map(make_term_path_piece, TermPath)),
+        TheTerm = words("the subterm")
+    ),
+    FromTypeStr =
+        mercury_type_to_string(TypeVarSet, print_name_only, FromType),
+    ToTypeStr =
+        mercury_type_to_string(TypeVarSet, print_name_only, ToType),
+    (
+        Reason = input_inst_not_ground(Inst),
+        ReasonPieces =
+            [TheTerm, words("has instantiatedness")] ++
+            report_inst(ModeInfo, quote_short_inst, [suffix(",")],
+                [nl_indent_delta(1)], [suffix(","), nl_indent_delta(-1)],
+                Inst) ++
+            [words("but it must be ground."), nl]
+    ;
+        Reason = invalid_inst_for_input_type(Inst),
+        ReasonPieces =
+            [TheTerm, words("has instantiatedness")] ++
+            report_inst(ModeInfo, quote_short_inst, [suffix(",")],
+                [nl_indent_delta(1)], [suffix(","), nl_indent_delta(-1)],
+                Inst) ++
+            [words("which is invalid for the type"), quote(FromTypeStr),
+            suffix("."), nl]
+    ;
+        Reason = invalid_cons_ids_for_result_type(ConsIds),
+        ConsIdsStrs = list.map(unqualify_cons_id_to_string, ConsIds),
+        ReasonPieces =
+            [words("cannot convert"), TheTerm,
+            words("from type"), quote(FromTypeStr),
+            words("to"), quote(ToTypeStr),
+            words("because its instantiatedness includes the function"),
+            words(choose_number(ConsIds, "symbol", "symbols"))] ++
+            list_to_quoted_pieces(ConsIdsStrs) ++
+            [suffix("."), nl]
+    ;
+        Reason = has_inst_expect_upcast(Inst),
+        ReasonPieces =
+            [words("cannot convert"), TheTerm,
+            words("from type"), quote(FromTypeStr),
+            words("to"), quote(ToTypeStr),
+            words("because it has instantiatedness")] ++
+            report_inst(ModeInfo, quote_short_inst, [suffix(",")],
+                [nl_indent_delta(1)], [suffix(","), nl_indent_delta(-1)],
+                Inst) ++
+            [words("and the former type is not a subtype of the latter type."),
+            nl]
+    ),
+    Pieces = [words("mode error:")] ++ TermPathPieces ++ ReasonPieces,
     Spec = simplest_spec($pred, severity_error,
         phase_mode_check(report_in_any_mode), Context, Preamble ++ Pieces).
 
-%---------------------------------------------------------------------------%
+:- func make_term_path_piece(coerce_error_term_path_step) =
+    list(format_component).
 
-:- func mode_error_coerce_ground_invalid_to_spec(mode_info, mer_inst, mer_type)
-    = error_spec.
+make_term_path_piece(Step) = Pieces :-
+    Step = coerce_error_term_path_step(ConsId, ArgNum),
+    ( if
+        ConsId = cons(_SymName, 1, _),
+        ArgNum = 1
+    then
+        ArgPieces = [words("in the argument")]
+    else
+        ArgPieces = [words("in argument"), int_fixed(ArgNum)]
+    ),
+    ConsIdStr = unqualify_cons_id_to_string(ConsId),
+    Pieces = ArgPieces ++
+        [words("of function symbol"), quote(ConsIdStr), suffix(":"), nl].
 
-mode_error_coerce_ground_invalid_to_spec(ModeInfo, Inst, Type) = Spec :-
-    Preamble = mode_info_context_preamble(ModeInfo),
-    mode_info_get_context(ModeInfo, Context),
-    varset.init(TypeVarSet),
-    Pieces = [words("mode error: the input term has instantiatedness")] ++
-        report_inst(ModeInfo, quote_short_inst, [suffix(","), nl],
-            [nl_indent_delta(1)], [suffix(","), nl_indent_delta(-1)],
-            Inst) ++
-        [words("and cannot be converted to the type"),
-        quote(mercury_type_to_string(TypeVarSet, print_name_only, Type)),
-        suffix("."), nl],
-    Spec = simplest_spec($pred, severity_error,
-        phase_mode_check(report_in_any_mode), Context, Preamble ++ Pieces).
+:- func unqualify_cons_id_to_string(cons_id) = string.
+
+unqualify_cons_id_to_string(ConsId0) = Str :-
+    ( if ConsId0 = cons(Name0, Arity, TypeCtor) then
+        UnqualName = unqualify_name(Name0),
+        Name = unqualified(UnqualName),
+        ConsId = cons(Name, Arity, TypeCtor)
+    else
+        ConsId = ConsId0
+    ),
+    Str = mercury_cons_id_to_string(output_mercury, does_not_need_brackets,
+        ConsId).
 
 %---------------------------------------------------------------------------%
 
diff --git a/compiler/modecheck_coerce.m b/compiler/modecheck_coerce.m
index f76b6beab..5f38f753d 100644
--- a/compiler/modecheck_coerce.m
+++ b/compiler/modecheck_coerce.m
@@ -53,6 +53,7 @@
 :- import_module parse_tree.prog_type_subst.
 :- import_module parse_tree.set_of_var.
 
+:- import_module int.
 :- import_module map.
 :- import_module maybe.
 :- import_module pair.
@@ -61,6 +62,9 @@
 :- import_module term.
 :- import_module varset.
 
+:- inst cons for cons_id/0
+    --->    cons(ground, ground, ground).
+
 :- type modecheck_coerce_result
     --->    coerce_mode_ok(
                 list(prog_var),
@@ -69,8 +73,11 @@
             )
     ;       coerce_mode_error.
 
-:- inst cons for cons_id/0
-    --->    cons(ground, ground, ground).
+:- type rev_term_path == list(coerce_error_term_path_step).
+
+:- type maybe_coerce_error(T)
+    --->    ok(T)
+    ;       coerce_error(coerce_error).
 
 %---------------------------------------------------------------------------%
 
@@ -79,14 +86,17 @@ modecheck_coerce(Args0, Args, Modes0, Modes, Det, ExtraGoals, !ModeInfo) :-
         mode_info_get_module_info(!.ModeInfo, ModuleInfo0),
         mode_info_get_instmap(!.ModeInfo, InstMap),
         ( if instmap_is_reachable(InstMap) then
+            mode_info_get_var_types(!.ModeInfo, VarTypes),
+            lookup_var_type(VarTypes, X, TypeX),
+            lookup_var_type(VarTypes, Y, TypeY),
             instmap_lookup_var(InstMap, X, InstX),
             instmap_lookup_var(InstMap, Y, InstY),
             ( if
                 inst_is_ground(ModuleInfo0, InstX),
                 not inst_is_clobbered(ModuleInfo0, InstX)
             then
-                modecheck_coerce_vars(ModuleInfo0, X, Y, InstX, InstY, Res,
-                    !ModeInfo),
+                modecheck_coerce_vars(ModuleInfo0, X, Y, TypeX, TypeY,
+                    InstX, InstY, Res, !ModeInfo),
                 (
                     Res = coerce_mode_ok(Args, Modes, ExtraGoals),
                     Det = detism_det
@@ -99,7 +109,9 @@ modecheck_coerce(Args0, Args, Modes0, Modes, Det, ExtraGoals, !ModeInfo) :-
                 )
             else
                 WaitingVars = set_of_var.make_singleton(X),
-                ModeError = mode_error_coerce_input_not_ground(X, InstX),
+                Reason = input_inst_not_ground(InstX),
+                Error = coerce_error([], TypeX, TypeY, Reason),
+                ModeError = mode_error_coerce_error(Error),
                 mode_info_error(WaitingVars, ModeError, !ModeInfo),
                 Args = [X, Y],
                 Modes = Modes0,
@@ -117,18 +129,15 @@ modecheck_coerce(Args0, Args, Modes0, Modes, Det, ExtraGoals, !ModeInfo) :-
     ).
 
 :- pred modecheck_coerce_vars(module_info::in, prog_var::in, prog_var::in,
-    mer_inst::in, mer_inst::in, modecheck_coerce_result::out,
-    mode_info::in, mode_info::out) is det.
+    mer_type::in, mer_type::in, mer_inst::in, mer_inst::in,
+    modecheck_coerce_result::out, mode_info::in, mode_info::out) is det.
 
-modecheck_coerce_vars(ModuleInfo0, X, Y, InstX, InstY, Res, !ModeInfo) :-
+modecheck_coerce_vars(ModuleInfo0, X, Y, TypeX, TypeY, InstX, InstY, Res,
+        !ModeInfo) :-
     mode_info_get_pred_id(!.ModeInfo, PredId),
     module_info_pred_info(ModuleInfo0, PredId, PredInfo),
     pred_info_get_typevarset(PredInfo, TVarSet),
 
-    mode_info_get_var_types(!.ModeInfo, VarTypes),
-    lookup_var_type(VarTypes, X, TypeX),
-    lookup_var_type(VarTypes, Y, TypeY),
-
     mode_info_var_is_live(!.ModeInfo, X, LiveX),
     mode_info_var_is_live(!.ModeInfo, Y, LiveY),
     ( if LiveX = is_live, LiveY = is_live then
@@ -137,11 +146,12 @@ modecheck_coerce_vars(ModuleInfo0, X, Y, InstX, InstY, Res, !ModeInfo) :-
         BothLive = is_dead
     ),
 
+    RevTermPath0 = [],
     set.init(ExpandedInsts0),
-    modecheck_coerce_make_inst(ModuleInfo0, TVarSet, LiveX, TypeX, TypeY,
-        ExpandedInsts0, InstX, MaybeFinalInstY),
+    modecheck_coerce_make_inst(ModuleInfo0, TVarSet, LiveX, RevTermPath0,
+        TypeX, TypeY, ExpandedInsts0, InstX, MaybeFinalInstY),
     (
-        MaybeFinalInstY = yes(FinalInstY),
+        MaybeFinalInstY = ok(FinalInstY),
         ( if
             abstractly_unify_inst(BothLive, InstX, ground_inst, real_unify,
                 UnifyInstX, _Det, ModuleInfo0, ModuleInfo1)
@@ -171,9 +181,9 @@ modecheck_coerce_vars(ModuleInfo0, X, Y, InstX, InstY, Res, !ModeInfo) :-
             Res = coerce_mode_ok([X, YPrime], [ModeX, ModeYPrime], ExtraGoals)
         )
     ;
-        MaybeFinalInstY = no,
-        ModeError = mode_error_coerce_ground_invalid(InstX, TypeY),
+        MaybeFinalInstY = coerce_error(Error),
         set_of_var.init(WaitingVars),
+        ModeError = mode_error_coerce_error(Error),
         mode_info_error(WaitingVars, ModeError, !ModeInfo),
         Res = coerce_mode_error
     ).
@@ -195,16 +205,16 @@ create_fresh_var(VarType, Var, !ModeInfo) :-
 
     % Try to produce the resulting inst of a coercion from TypeX to TypeY.
     % InstX is the initial inst of the input term.
-    % If the coercion is mode correct, then Res is bound to 'yes(InstY)' where
+    % If the coercion is mode correct, then Res is bound to 'ok(InstY)' where
     % InstY is the final inst of the result term.
-    % If there is a mode error, Res is bound to 'no'.
+    % If there is a mode error, Res is bound to 'coerce_error(Error)'.
     %
 :- pred modecheck_coerce_make_inst(module_info::in, tvarset::in, is_live::in,
-    mer_type::in, mer_type::in, expanded_insts::in,
-    mer_inst::in, maybe(mer_inst)::out) is det.
+    rev_term_path::in, mer_type::in, mer_type::in, expanded_insts::in,
+    mer_inst::in, maybe_coerce_error(mer_inst)::out) is det.
 
-modecheck_coerce_make_inst(ModuleInfo, TVarSet, LiveX, TypeX, TypeY,
-        ExpandedInsts0, InstX, Res) :-
+modecheck_coerce_make_inst(ModuleInfo, TVarSet, LiveX, RevTermPath0,
+        TypeX, TypeY, ExpandedInsts0, InstX, Res) :-
     (
         ( InstX = free
         ; InstX = free(_)
@@ -216,24 +226,25 @@ modecheck_coerce_make_inst(ModuleInfo, TVarSet, LiveX, TypeX, TypeY,
     ;
         InstX = bound(UniqX, _InstResultsX, FunctorsX),
         modecheck_coerce_from_bound_make_bound_inst(ModuleInfo, TVarSet, LiveX,
-            UniqX, TypeX, TypeY, ExpandedInsts0, FunctorsX, Res)
+            UniqX, RevTermPath0, TypeX, TypeY, ExpandedInsts0, InstX,
+            FunctorsX, Res)
     ;
         InstX = ground(UniqX, HOInstInfoX),
         (
             HOInstInfoX = none_or_default_func,
-            modecheck_coerce_from_ground_make_inst(ModuleInfo, TVarSet,
-                LiveX, UniqX, TypeX, TypeY, Res)
+            modecheck_coerce_from_ground_make_inst(ModuleInfo, TVarSet, LiveX,
+                UniqX, RevTermPath0, TypeX, TypeY, InstX, Res)
         ;
             HOInstInfoX = higher_order(_PredInstInfoX),
             UniqY = uniqueness_for_coerce_result(LiveX, UniqX),
             % Coerce cannot change the calling convention.
             InstY = ground(UniqY, HOInstInfoX),
-            Res = yes(InstY)
+            Res = ok(InstY)
         )
     ;
         InstX = not_reached,
         InstY = not_reached,
-        Res = yes(InstY)
+        Res = ok(InstY)
     ;
         InstX = inst_var(_),
         unexpected($pred, "uninstantiated inst parameter")
@@ -242,15 +253,15 @@ modecheck_coerce_make_inst(ModuleInfo, TVarSet, LiveX, TypeX, TypeY,
         % The input term of TypeX is approximated by a ground inst SubInstX.
         % After conversion, the result term of TypeY must be approximated by
         % a ground inst SubInstY.
-        modecheck_coerce_make_inst(ModuleInfo, TVarSet, LiveX, TypeX, TypeY,
-            ExpandedInsts0, SubInstX, SubRes),
+        modecheck_coerce_make_inst(ModuleInfo, TVarSet, LiveX, RevTermPath0,
+            TypeX, TypeY, ExpandedInsts0, SubInstX, SubRes),
         (
-            SubRes = yes(SubInstY),
+            SubRes = ok(SubInstY),
             InstY = constrained_inst_vars(InstVars, SubInstY),
-            Res = yes(InstY)
+            Res = ok(InstY)
         ;
-            SubRes = no,
-            Res = no
+            SubRes = coerce_error(Error),
+            Res = coerce_error(Error)
         )
     ;
         InstX = defined_inst(InstNameX),
@@ -261,24 +272,28 @@ modecheck_coerce_make_inst(ModuleInfo, TVarSet, LiveX, TypeX, TypeY,
         then
             inst_lookup(ModuleInfo, InstNameX, InstX1),
             modecheck_coerce_make_inst(ModuleInfo, TVarSet, LiveX,
-                TypeX, TypeY, ExpandedInsts1, InstX1, Res)
+                RevTermPath0, TypeX, TypeY, ExpandedInsts1, InstX1, Res)
         else
             % If we would enter an infinite loop, return the inst unexpanded.
             % A recursive check, by definition, cannot find any errors that the
             % nonrecursive part of the algorithm would not find.
             %
-            % Just in case, ensure that we are definitely dealing with
-            % user-defined inst; and TypeX =< TypeY so an inst valid for
-            % TypeX must be valid for TypeY.
-            % The sanity checks might not be strictly necessary.
-            ( if
-                is_user_inst(InstNameX),
-                check_is_subtype(ModuleInfo, TVarSet, TypeX, TypeY)
-            then
-                InstY = defined_inst(InstNameX),
-                Res = yes(InstY)
+            % Ensure that we are definitely dealing with user-defined inst.
+            % This check might not be strictly necessary.
+            ( if is_user_inst(InstNameX) then
+                % If TypeX =< TypeY then an inst valid for TypeX must be
+                % valid for TypeY.
+                ( if check_is_subtype(ModuleInfo, TVarSet, TypeX, TypeY) then
+                    InstY = defined_inst(InstNameX),
+                    Res = ok(InstY)
+                else
+                    list.reverse(RevTermPath0, TermPath),
+                    Reason = has_inst_expect_upcast(InstX),
+                    Error = coerce_error(TermPath, TypeX, TypeY, Reason),
+                    Res = coerce_error(Error)
+                )
             else
-                Res = no
+                unexpected($pred, "not user-defined inst")
             )
         )
     ;
@@ -339,82 +354,138 @@ is_user_inst(InstName) :-
     % if the conversion is valid.
     %
 :- pred modecheck_coerce_from_bound_make_bound_inst(module_info::in,
-    tvarset::in, is_live::in, uniqueness::in, mer_type::in, mer_type::in,
-    expanded_insts::in, list(bound_inst)::in, maybe(mer_inst)::out) is det.
+    tvarset::in, is_live::in, uniqueness::in, rev_term_path::in,
+    mer_type::in, mer_type::in, expanded_insts::in, mer_inst::in,
+    list(bound_inst)::in, maybe_coerce_error(mer_inst)::out) is det.
 
 modecheck_coerce_from_bound_make_bound_inst(ModuleInfo, TVarSet, LiveX, UniqX,
-        TypeX, TypeY, ExpandedInsts0, FunctorsX, Res) :-
-    ( if
-        modecheck_coerce_from_bound_make_bound_functors(ModuleInfo, TVarSet,
-            LiveX, TypeX, TypeY, ExpandedInsts0, FunctorsX, FunctorsY)
-    then
-        UniqY = uniqueness_for_coerce_result(LiveX, UniqX),
-        % XXX A better approximation of InstResults is probably possible.
-        InstResults = inst_test_results(
-            inst_result_is_ground,
-            inst_result_does_not_contain_any,
-            inst_result_contains_inst_names_unknown,
-            inst_result_contains_inst_vars_unknown,
-            inst_result_contains_types_unknown,
-            inst_result_no_type_ctor_propagated
-        ),
-        InstY = bound(UniqY, InstResults, FunctorsY),
-        Res = yes(InstY)
-    else
-        Res = no
+        RevTermPath0, TypeX, TypeY, ExpandedInsts0, InstX, FunctorsX, Res) :-
+    modecheck_coerce_from_bound_make_bound_functors(ModuleInfo, TVarSet, LiveX,
+        RevTermPath0, TypeX, TypeY, ExpandedInsts0, InstX,
+        FunctorsX, FunctorsY, BadConsIds, no, MaybeError0),
+    (
+        BadConsIds = [],
+        (
+            MaybeError0 = no,
+            UniqY = uniqueness_for_coerce_result(LiveX, UniqX),
+            % XXX A better approximation of InstResults is probably possible.
+            InstResults = inst_test_results(
+                inst_result_is_ground,
+                inst_result_does_not_contain_any,
+                inst_result_contains_inst_names_unknown,
+                inst_result_contains_inst_vars_unknown,
+                inst_result_contains_types_unknown,
+                inst_result_no_type_ctor_propagated
+            ),
+            InstY = bound(UniqY, InstResults, FunctorsY),
+            Res = ok(InstY)
+        ;
+            MaybeError0 = yes(Error),
+            Res = coerce_error(Error)
+        )
+    ;
+        BadConsIds = [_ | _],
+        % This will prefer to report any invalid functors in the current
+        % `bound()' inst over an error found deeper in the inst tree
+        % (in MaybeError0).
+        list.reverse(RevTermPath0, TermPath),
+        Reason = invalid_cons_ids_for_result_type(BadConsIds),
+        Error = coerce_error(TermPath, TypeX, TypeY, Reason),
+        Res = coerce_error(Error)
     ).
 
-    % XXX try to report which functors cause the coercion to be invalid
-    %
 :- pred modecheck_coerce_from_bound_make_bound_functors(module_info::in,
-    tvarset::in, is_live::in, mer_type::in, mer_type::in, expanded_insts::in,
-    list(bound_inst)::in, list(bound_inst)::out) is semidet.
+    tvarset::in, is_live::in, rev_term_path::in, mer_type::in, mer_type::in,
+    expanded_insts::in, mer_inst::in,
+    list(bound_inst)::in, list(bound_inst)::out, list(cons_id)::out,
+    maybe(coerce_error)::in, maybe(coerce_error)::out) is det.
 
 modecheck_coerce_from_bound_make_bound_functors(ModuleInfo, TVarSet, LiveX,
-        TypeX, TypeY, ExpandedInsts0, FunctorsX, FunctorsY) :-
+        RevTermPath0, TypeX, TypeY, ExpandedInsts0, InstX,
+        FunctorsX, FunctorsY, BadConsIds, !MaybeError) :-
     (
         FunctorsX = [],
-        FunctorsY = []
+        FunctorsY = [],
+        BadConsIds = []
     ;
         FunctorsX = [HeadFunctorX | TailFunctorsX],
         modecheck_coerce_from_bound_make_bound_functor(ModuleInfo, TVarSet,
-            LiveX, TypeX, TypeY, ExpandedInsts0,
-            HeadFunctorX, yes(HeadFunctorY)),
+            LiveX, RevTermPath0, TypeX, TypeY, ExpandedInsts0, InstX,
+            HeadFunctorX, MaybeHeadFunctorY, !MaybeError),
+        % Check remaining functors in this `bound()' inst so that we can
+        % report multiple invalid functors together.
         modecheck_coerce_from_bound_make_bound_functors(ModuleInfo, TVarSet,
-            LiveX, TypeX, TypeY, ExpandedInsts0,
-            TailFunctorsX, TailFunctorsY),
-        FunctorsY = [HeadFunctorY | TailFunctorsY]
+            LiveX, RevTermPath0, TypeX, TypeY, ExpandedInsts0, InstX,
+            TailFunctorsX, TailFunctorsY, TailBadConsIds, !MaybeError),
+        (
+            MaybeHeadFunctorY = ok(HeadFunctorY),
+            FunctorsY = [HeadFunctorY | TailFunctorsY],
+            BadConsIds = TailBadConsIds
+        ;
+            MaybeHeadFunctorY = bad_cons_id(HeadBadConsId),
+            FunctorsY = TailFunctorsY,
+            BadConsIds = [HeadBadConsId | TailBadConsIds]
+        ;
+            MaybeHeadFunctorY = other_error,
+            FunctorsY = TailFunctorsY,
+            BadConsIds = TailBadConsIds
+        )
     ).
 
+:- type bound_inst_or_error
+    --->    ok(bound_inst)
+    ;       bad_cons_id(cons_id)
+    ;       other_error.    % error kept separately
+
 :- pred modecheck_coerce_from_bound_make_bound_functor(module_info::in,
-    tvarset::in, is_live::in, mer_type::in, mer_type::in, expanded_insts::in,
-    bound_inst::in, maybe(bound_inst)::out) is det.
+    tvarset::in, is_live::in, rev_term_path::in, mer_type::in, mer_type::in,
+    expanded_insts::in, mer_inst::in, bound_inst::in, bound_inst_or_error::out,
+    maybe(coerce_error)::in, maybe(coerce_error)::out) is det.
 
 modecheck_coerce_from_bound_make_bound_functor(ModuleInfo, TVarSet, LiveX,
-        TypeX, TypeY, ExpandedInsts0, FunctorX, Res) :-
+        RevTermPath0, TypeX, TypeY, ExpandedInsts0, InstX, FunctorX,
+        MaybeFunctorY, !MaybeError) :-
     FunctorX = bound_functor(ConsIdX, ArgInstsX),
     % The user may have provided an inst that does not make sense for the type.
     % The compiler does not check for that elsewhere (probably it should)
     % so we try to be careful about that here.
     (
         ConsIdX = cons(_, _, _),
-        ( if
-            get_bound_functor_cons_and_arg_types(ModuleInfo, TypeX, TypeY,
-                ConsIdX, ConsIdY, ArgTypesX, ArgTypesY, Arity),
-            list.length(ArgInstsX, Arity)
-        then
-            ( if
+        get_bound_functor_cons_and_arg_types(ModuleInfo, TypeX, TypeY,
+            ConsIdX, ConsIdY, GetArgTypesRes),
+        (
+            GetArgTypesRes = arg_types(ArgTypesX, ArgTypesY, Arity),
+            ( if list.length(ArgInstsX, Arity) then
                 modecheck_coerce_from_bound_make_bound_functor_arg_insts(
-                    ModuleInfo, TVarSet, LiveX, ExpandedInsts0,
-                    ArgTypesX, ArgTypesY, ArgInstsX, ArgInstsY)
-            then
-                FunctorY = bound_functor(ConsIdY, ArgInstsY),
-                Res = yes(FunctorY)
+                    ModuleInfo, TVarSet, LiveX, RevTermPath0, ExpandedInsts0,
+                    ConsIdX, 1, ArgTypesX, ArgTypesY,
+                    ArgInstsX, MaybeArgInstsY),
+                (
+                    MaybeArgInstsY = ok(ArgInstsY),
+                    FunctorY = bound_functor(ConsIdY, ArgInstsY),
+                    MaybeFunctorY = ok(FunctorY)
+                ;
+                    MaybeArgInstsY = coerce_error(Error),
+                    maybe_keep_error(Error, !MaybeError),
+                    MaybeFunctorY = other_error
+                )
             else
-                Res = no
+                list.reverse(RevTermPath0, TermPath),
+                Reason = invalid_inst_for_input_type(InstX),
+                Error = coerce_error(TermPath, TypeX, TypeY, Reason),
+                maybe_keep_error(Error, !MaybeError),
+                MaybeFunctorY = other_error
             )
-        else
-            Res = no
+        ;
+            GetArgTypesRes = bad_cons_id_for_input_type,
+            list.reverse(RevTermPath0, TermPath),
+            Reason = invalid_inst_for_input_type(InstX),
+            Error = coerce_error(TermPath, TypeX, TypeY, Reason),
+            maybe_keep_error(Error, !MaybeError),
+            MaybeFunctorY = other_error
+        ;
+            GetArgTypesRes = bad_cons_id_for_result_type,
+            MaybeFunctorY = bad_cons_id(ConsIdY)
         )
     ;
         ConsIdX = tuple_cons(_),
@@ -436,15 +507,19 @@ modecheck_coerce_from_bound_make_bound_functor(ModuleInfo, TVarSet, LiveX,
         ; ConsIdX = char_const(_)
         ; ConsIdX = string_const(_)
         ),
-        ( if
-            cons_id_matches_builtin_type(ConsIdX, TypeX),
-            TypeX = TypeY,
-            ArgInstsX = []
-        then
+        ( if cons_id_matches_builtin_type(ConsIdX, TypeX) then
+            expect(unify(TypeX, TypeY), $pred,
+                "coercion between different builtin types"),
+            expect(unify(ArgInstsX, []), $pred,
+                "bound functor literal has arguments"),
             FunctorY = FunctorX,
-            Res = yes(FunctorY)
+            MaybeFunctorY = ok(FunctorY)
         else
-            Res = no
+            list.reverse(RevTermPath0, TermPath),
+            Reason = invalid_inst_for_input_type(InstX),
+            Error = coerce_error(TermPath, TypeX, TypeY, Reason),
+            maybe_keep_error(Error, !MaybeError),
+            MaybeFunctorY = other_error
         )
     ;
         ( ConsIdX = closure_cons(_, _)
@@ -463,84 +538,145 @@ modecheck_coerce_from_bound_make_bound_functor(ModuleInfo, TVarSet, LiveX,
         unexpected($pred, "unsupported cons_id")
     ).
 
+:- type get_arg_types_result
+    --->    arg_types(list(mer_type), list(mer_type), int)
+    ;       bad_cons_id_for_input_type
+    ;       bad_cons_id_for_result_type.
+
 :- pred get_bound_functor_cons_and_arg_types(module_info::in,
     mer_type::in, mer_type::in, cons_id::in(cons), cons_id::out(cons),
-    list(mer_type)::out, list(mer_type)::out, int::out) is semidet.
+    get_arg_types_result::out) is det.
 
 get_bound_functor_cons_and_arg_types(ModuleInfo, TypeX, TypeY,
-        ConsIdX, ConsIdY, ArgTypesX, ArgTypesY, Arity) :-
-    type_to_ctor(TypeY, TypeCtorY),
+        ConsIdX, ConsIdY, Res) :-
+    type_to_ctor_det(TypeY, TypeCtorY),
     make_cons_id_for_type_ctor(TypeCtorY, ConsIdX, ConsIdY),
-    require_complete_switch [TypeX]
     (
         TypeX = defined_type(_, _, _),
-        TypeY = defined_type(_, _, _),
-        % This fails if the input type does not actually have the
-        % functor given in a `bound' inst.
-        get_ctor_arg_types_do_subst(ModuleInfo, TypeX, ConsIdX,
-            ArgTypesX),
-        % This fails if the result type does not have a constructor
-        % matching that of the input type.
-        get_ctor_arg_types_do_subst(ModuleInfo, TypeY, ConsIdY,
-            ArgTypesY),
-        list.length(ArgTypesX, Arity),
-        list.length(ArgTypesY, Arity)
+        ( if
+            % This fails if the input type does not actually have the
+            % functor given in a `bound' inst.
+            get_ctor_arg_types_do_subst(ModuleInfo, TypeX, ConsIdX,
+                ArgTypesX)
+        then
+            ( if
+                TypeY = defined_type(_, _, _),
+                % This fails if the result type does not have a constructor
+                % matching that of the input type.
+                get_ctor_arg_types_do_subst(ModuleInfo, TypeY, ConsIdY,
+                    ArgTypesY)
+            then
+                ( if
+                    list.length(ArgTypesX, Arity),
+                    list.length(ArgTypesY, Arity)
+                then
+                    Res = arg_types(ArgTypesX, ArgTypesY, Arity)
+                else
+                    unexpected($pred, "arg types length mismatch")
+                )
+            else
+                Res = bad_cons_id_for_result_type
+            )
+        else
+            Res = bad_cons_id_for_input_type
+        )
     ;
         TypeX = tuple_type(ArgTypesX, _),
-        TypeY = tuple_type(ArgTypesY, _),
-        ConsIdX = cons(unqualified("{}"), Arity, _),
-        list.length(ArgTypesX, Arity),
-        list.length(ArgTypesY, Arity)
+        ( if
+            ConsIdX = cons(unqualified("{}"), Arity, _),
+            list.length(ArgTypesX, Arity)
+        then
+            ( if
+                TypeY = tuple_type(ArgTypesY, _),
+                list.length(ArgTypesY, Arity)
+            then
+                Res = arg_types(ArgTypesX, ArgTypesY, Arity)
+            else
+                unexpected($pred, "tuple type mismatch")
+            )
+        else
+            Res = bad_cons_id_for_input_type
+        )
     ;
         TypeX = builtin_type(BuiltinType),
-        TypeY = builtin_type(BuiltinType),
-        % `cons' is used for char.
-        BuiltinType = builtin_type_char,
-        ConsIdX = cons(_SymName, Arity, _),
-        Arity = 0,
-        ArgTypesX = [],
-        ArgTypesY = []
+        expect(unify(TypeX, TypeY), $pred,
+            "coercion between different builtin types"),
+        (
+            BuiltinType = builtin_type_char,
+            ConsIdX = cons(_SymName, Arity, _),
+            ( if Arity = 0 then
+                ArgTypesX = [],
+                ArgTypesY = [],
+                Res = arg_types(ArgTypesX, ArgTypesY, Arity)
+            else
+                Res = bad_cons_id_for_input_type
+            )
+        ;
+            ( BuiltinType = builtin_type_int(_)
+            ; BuiltinType = builtin_type_float
+            ; BuiltinType = builtin_type_string
+            ),
+            Res = bad_cons_id_for_input_type
+        )
     ;
         TypeX = kinded_type(TypeX1, Kind),
-        TypeY = kinded_type(TypeY1, Kind),
-        get_bound_functor_cons_and_arg_types(ModuleInfo, TypeX1, TypeY1,
-            ConsIdX, ConsIdY, ArgTypesX, ArgTypesY, Arity)
+        ( if TypeY = kinded_type(TypeY1, Kind) then
+            get_bound_functor_cons_and_arg_types(ModuleInfo, TypeX1, TypeY1,
+                ConsIdX, _ConsIdY, Res)
+        else
+            sorry($pred, "kinded type mismatch")
+        )
     ;
         ( TypeX = type_variable(_, _)
         ; TypeX = higher_order_type(_, _, _, _, _)
         ; TypeX = apply_n_type(_, _, _)
         ),
-        fail
+        Res = bad_cons_id_for_input_type
     ).
 
-    % XXX try to report which arguments cause the coercion to be invalid
-    %
 :- pred modecheck_coerce_from_bound_make_bound_functor_arg_insts(
-    module_info::in, tvarset::in, is_live::in, expanded_insts::in,
+    module_info::in, tvarset::in, is_live::in, rev_term_path::in,
+    expanded_insts::in, cons_id::in, int::in,
     list(mer_type)::in, list(mer_type)::in,
-    list(mer_inst)::in, list(mer_inst)::out) is semidet.
+    list(mer_inst)::in, maybe_coerce_error(list(mer_inst))::out) is det.
 
 modecheck_coerce_from_bound_make_bound_functor_arg_insts(ModuleInfo, TVarSet,
-        LiveX, ExpandedInsts0, ArgTypesX, ArgTypesY, ArgInstsX, ArgInstsY) :-
+        LiveX, RevTermPath0, ExpandedInsts0, ConsIdX, CurArgNum,
+        ArgTypesX, ArgTypesY, ArgInstsX, MaybeArgInstsY) :-
     ( if
         ArgTypesX = [],
         ArgTypesY = [],
         ArgInstsX = []
     then
-        ArgInstsY = []
+        MaybeArgInstsY = ok([])
     else if
         ArgTypesX = [HeadArgTypeX | TailArgTypesX],
         ArgTypesY = [HeadArgTypeY | TailArgTypesY],
         ArgInstsX = [HeadArgInstX | TailArgInstsX]
     then
-        modecheck_coerce_make_inst(ModuleInfo, TVarSet, LiveX,
+        Step = coerce_error_term_path_step(ConsIdX, CurArgNum),
+        RevTermPath1 = [Step | RevTermPath0],
+        modecheck_coerce_make_inst(ModuleInfo, TVarSet, LiveX, RevTermPath1,
             HeadArgTypeX, HeadArgTypeY, ExpandedInsts0,
-            HeadArgInstX, yes(HeadArgInstY)),
-        modecheck_coerce_from_bound_make_bound_functor_arg_insts(ModuleInfo,
-            TVarSet, LiveX, ExpandedInsts0,
-            TailArgTypesX, TailArgTypesY,
-            TailArgInstsX, TailArgInstsY),
-        ArgInstsY = [HeadArgInstY | TailArgInstsY]
+            HeadArgInstX, MaybeHeadArgInstY),
+        (
+            MaybeHeadArgInstY = ok(HeadArgInstY),
+            modecheck_coerce_from_bound_make_bound_functor_arg_insts(ModuleInfo,
+                TVarSet, LiveX, RevTermPath0, ExpandedInsts0,
+                ConsIdX, CurArgNum + 1,
+                TailArgTypesX, TailArgTypesY,
+                TailArgInstsX, MaybeTailArgInstsY),
+            (
+                MaybeTailArgInstsY = ok(TailArgInstsY),
+                MaybeArgInstsY = ok([HeadArgInstY | TailArgInstsY])
+            ;
+                MaybeTailArgInstsY = coerce_error(Error),
+                MaybeArgInstsY = coerce_error(Error)
+            )
+        ;
+            MaybeHeadArgInstY = coerce_error(Error),
+            MaybeArgInstsY = coerce_error(Error)
+        )
     else
         unexpected($pred, "length mismatch")
     ).
@@ -563,6 +699,20 @@ cons_id_matches_builtin_type(ConsId, Type) :-
     ; ConsId = string_const(_), Type = string_type
     ).
 
+:- pred maybe_keep_error(coerce_error::in,
+    maybe(coerce_error)::in, maybe(coerce_error)::out) is det.
+
+maybe_keep_error(Error, MaybeError0, MaybeError) :-
+    (
+        MaybeError0 = no,
+        MaybeError = yes(Error)
+    ;
+        MaybeError0 = yes(_Error0),
+        % We only keep one error as only one error message will be reported
+        % for a coerce expression.
+        MaybeError = MaybeError0
+    ).
+
 %---------------------------------------------------------------------------%
 
     % "from_ground" refers to the fact that the input term has the inst
@@ -570,23 +720,26 @@ cons_id_matches_builtin_type(ConsId, Type) :-
     % conversion is valid.
     %
 :- pred modecheck_coerce_from_ground_make_inst(module_info::in, tvarset::in,
-    is_live::in, uniqueness::in, mer_type::in, mer_type::in,
-    maybe(mer_inst)::out) is det.
+    is_live::in, uniqueness::in, rev_term_path::in, mer_type::in, mer_type::in,
+    mer_inst::in, maybe_coerce_error(mer_inst)::out) is det.
 
 modecheck_coerce_from_ground_make_inst(ModuleInfo, TVarSet, LiveX, UniqX,
-        TypeX, TypeY, MaybeInstY) :-
+        RevTermPath0, TypeX, TypeY, InstX, MaybeInstY) :-
     ( if TypeX = TypeY then
         % This should be a common case.
         UniqY = uniqueness_for_coerce_result(LiveX, UniqX),
         InstY = ground(UniqY, none_or_default_func),
-        MaybeInstY = yes(InstY)
+        MaybeInstY = ok(InstY)
     else if check_is_subtype(ModuleInfo, TVarSet, TypeX, TypeY) then
         set.init(SeenTypes0),
         modecheck_coerce_from_ground_make_inst_for_subtype(ModuleInfo, TVarSet,
             LiveX, UniqX, SeenTypes0, TypeX, TypeY, InstY),
-        MaybeInstY = yes(InstY)
+        MaybeInstY = ok(InstY)
     else
-        MaybeInstY = no
+        list.reverse(RevTermPath0, TermPath),
+        Reason = has_inst_expect_upcast(InstX),
+        Error = coerce_error(TermPath, TypeX, TypeY, Reason),
+        MaybeInstY = coerce_error(Error)
     ).
 
     % Precondition: TypeX =< TypeY.
diff --git a/tests/invalid/Mmakefile b/tests/invalid/Mmakefile
index 16b917ec4..361c21c3a 100644
--- a/tests/invalid/Mmakefile
+++ b/tests/invalid/Mmakefile
@@ -125,6 +125,7 @@ SINGLEMODULE= \
 	coerce_instvar \
 	coerce_int \
 	coerce_mode_error \
+	coerce_mode_error2 \
 	coerce_non_du \
 	coerce_recursive_inst \
 	coerce_recursive_type \
diff --git a/tests/invalid/coerce_clobbered.err_exp b/tests/invalid/coerce_clobbered.err_exp
index 2bc874620..fd7140fa4 100644
--- a/tests/invalid/coerce_clobbered.err_exp
+++ b/tests/invalid/coerce_clobbered.err_exp
@@ -1,4 +1,4 @@
 coerce_clobbered.m:021: In clause for `bad(in(clobbered), out)':
 coerce_clobbered.m:021:   in coerce:
-coerce_clobbered.m:021:   mode error: `X' has instantiatedness `clobbered',
-coerce_clobbered.m:021:   but it must be ground.
+coerce_clobbered.m:021:   mode error: the input term has instantiatedness
+coerce_clobbered.m:021:   `clobbered', but it must be ground.
diff --git a/tests/invalid/coerce_instvar.err_exp b/tests/invalid/coerce_instvar.err_exp
index 292f9a29d..0cb0e84a7 100644
--- a/tests/invalid/coerce_instvar.err_exp
+++ b/tests/invalid/coerce_instvar.err_exp
@@ -1,7 +1,7 @@
 coerce_instvar.m:043: In clause for `bad(in((I =< ground)), out((I =<
 coerce_instvar.m:043:   ground)))':
 coerce_instvar.m:043:   in coerce:
-coerce_instvar.m:043:   mode error: the input term has instantiatedness
-coerce_instvar.m:043:   `( I =< ground )',
-coerce_instvar.m:043:   and cannot be converted to the type
-coerce_instvar.m:043:   `coerce_instvar.citrus'.
+coerce_instvar.m:043:   mode error: cannot convert the input term from type
+coerce_instvar.m:043:   `coerce_instvar.fruit' to `coerce_instvar.citrus'
+coerce_instvar.m:043:   because it has instantiatedness `ground', and the
+coerce_instvar.m:043:   former type is not a subtype of the latter type.
diff --git a/tests/invalid/coerce_int.err_exp b/tests/invalid/coerce_int.err_exp
index 197c8bc8d..a93597419 100644
--- a/tests/invalid/coerce_int.err_exp
+++ b/tests/invalid/coerce_int.err_exp
@@ -24,23 +24,7 @@ coerce_int.m:026:       ).
 coerce_int.m:032: In clause for `bad_wrong_type(in(coerce_int.wrap(bound(1 ;
 coerce_int.m:032:   2)))) = out(coerce_int.wrap(bound(1 ; 2)))':
 coerce_int.m:032:   in coerce:
-coerce_int.m:032:   mode error: the input term has instantiatedness
-coerce_int.m:032:     named inst wrap(
-coerce_int.m:032:       bound(
-coerce_int.m:032:         1
-coerce_int.m:032:       ;
-coerce_int.m:032:         2
-coerce_int.m:032:       )
-coerce_int.m:032:     ),
-coerce_int.m:032:     which expands to
-coerce_int.m:032:       bound(
-coerce_int.m:032:         wrap(
-coerce_int.m:032:           bound(
-coerce_int.m:032:             1
-coerce_int.m:032:           ;
-coerce_int.m:032:             2
-coerce_int.m:032:           )
-coerce_int.m:032:         )
-coerce_int.m:032:       ),
-coerce_int.m:032:   and cannot be converted to the type
-coerce_int.m:032:   `coerce_int.wrap(uint)'.
+coerce_int.m:032:   mode error: in the input term:
+coerce_int.m:032:   in the argument of function symbol `wrap':
+coerce_int.m:032:   the subterm has instantiatedness `bound(1 ; 2)', which is
+coerce_int.m:032:   invalid for the type `uint'.
diff --git a/tests/invalid/coerce_mode_error.err_exp b/tests/invalid/coerce_mode_error.err_exp
index 0899016bb..46f750ea0 100644
--- a/tests/invalid/coerce_mode_error.err_exp
+++ b/tests/invalid/coerce_mode_error.err_exp
@@ -1,28 +1,37 @@
 coerce_mode_error.m:035: In clause for `bad_coerce_free_input(in(free), out)':
 coerce_mode_error.m:035:   in coerce:
-coerce_mode_error.m:035:   mode error: `X' has instantiatedness `free',
-coerce_mode_error.m:035:   but it must be ground.
+coerce_mode_error.m:035:   mode error: the input term has instantiatedness
+coerce_mode_error.m:035:   `free', but it must be ground.
 coerce_mode_error.m:046: In clause for `bad_fruit_to_citrus(in, out)':
 coerce_mode_error.m:046:   in coerce:
-coerce_mode_error.m:046:   mode error: the input term has instantiatedness
-coerce_mode_error.m:046:   `ground',
-coerce_mode_error.m:046:   and cannot be converted to the type
-coerce_mode_error.m:046:   `coerce_mode_error.citrus'.
+coerce_mode_error.m:046:   mode error: cannot convert the input term from type
+coerce_mode_error.m:046:   `coerce_mode_error.fruit' to
+coerce_mode_error.m:046:   `coerce_mode_error.citrus' because it has
+coerce_mode_error.m:046:   instantiatedness `ground', and the former type is
+coerce_mode_error.m:046:   not a subtype of the latter type.
 coerce_mode_error.m:052: In clause for `bad_nelist_f_to_list_c(in, out)':
 coerce_mode_error.m:052:   in coerce:
-coerce_mode_error.m:052:   mode error: the input term has instantiatedness
-coerce_mode_error.m:052:   `ground',
-coerce_mode_error.m:052:   and cannot be converted to the type
-coerce_mode_error.m:052:   `coerce_mode_error.list(coerce_mode_error.citrus)'.
+coerce_mode_error.m:052:   mode error: cannot convert the input term from type
+coerce_mode_error.m:052:   `coerce_mode_error.non_empty_list(coerce_mode_error.fruit)'
+coerce_mode_error.m:052:   to
+coerce_mode_error.m:052:   `coerce_mode_error.list(coerce_mode_error.citrus)'
+coerce_mode_error.m:052:   because it has instantiatedness `ground', and the
+coerce_mode_error.m:052:   former type is not a subtype of the latter type.
 coerce_mode_error.m:064: In clause for `bad_from_list_to_least2(in, out)':
 coerce_mode_error.m:064:   in coerce:
-coerce_mode_error.m:064:   mode error: the input term has instantiatedness
-coerce_mode_error.m:064:   `bound('[|]'(ground, ground))',
-coerce_mode_error.m:064:   and cannot be converted to the type
-coerce_mode_error.m:064:   `coerce_mode_error.least2(V_1)'.
+coerce_mode_error.m:064:   mode error: in the input term:
+coerce_mode_error.m:064:   in argument 2 of function symbol `'[|]'':
+coerce_mode_error.m:064:   cannot convert the subterm from type
+coerce_mode_error.m:064:   `coerce_mode_error.list(V_1)' to
+coerce_mode_error.m:064:   `coerce_mode_error.non_empty_list(V_1)' because it
+coerce_mode_error.m:064:   has instantiatedness `ground', and the former type
+coerce_mode_error.m:064:   is not a subtype of the latter type.
 coerce_mode_error.m:073: In clause for `from_list_to_least2(in, out)':
 coerce_mode_error.m:073:   in coerce:
-coerce_mode_error.m:073:   mode error: the input term has instantiatedness
-coerce_mode_error.m:073:   `bound('[|]'(ground, ground))',
-coerce_mode_error.m:073:   and cannot be converted to the type
-coerce_mode_error.m:073:   `coerce_mode_error.least2(V_1)'.
+coerce_mode_error.m:073:   mode error: in the input term:
+coerce_mode_error.m:073:   in argument 2 of function symbol `'[|]'':
+coerce_mode_error.m:073:   cannot convert the subterm from type
+coerce_mode_error.m:073:   `coerce_mode_error.list(V_1)' to
+coerce_mode_error.m:073:   `coerce_mode_error.non_empty_list(V_1)' because it
+coerce_mode_error.m:073:   has instantiatedness `ground', and the former type
+coerce_mode_error.m:073:   is not a subtype of the latter type.
diff --git a/tests/invalid/coerce_mode_error2.err_exp b/tests/invalid/coerce_mode_error2.err_exp
new file mode 100644
index 000000000..22a743f05
--- /dev/null
+++ b/tests/invalid/coerce_mode_error2.err_exp
@@ -0,0 +1,16 @@
+coerce_mode_error2.m:039: In clause for `test1(out)':
+coerce_mode_error2.m:039:   in coerce:
+coerce_mode_error2.m:039:   mode error: in the input term:
+coerce_mode_error2.m:039:   in argument 2 of function symbol `foo':
+coerce_mode_error2.m:039:   cannot convert the subterm from type
+coerce_mode_error2.m:039:   `coerce_mode_error2.fruit' to
+coerce_mode_error2.m:039:   `coerce_mode_error2.citrus' because its
+coerce_mode_error2.m:039:   instantiatedness includes the function symbols
+coerce_mode_error2.m:039:   `apple' and `banana'.
+coerce_mode_error2.m:050: In clause for `test2(out)':
+coerce_mode_error2.m:050:   in coerce:
+coerce_mode_error2.m:050:   mode error: cannot convert the input term from type
+coerce_mode_error2.m:050:   `coerce_mode_error2.foo(coerce_mode_error2.fruit)'
+coerce_mode_error2.m:050:   to `coerce_mode_error2.foo_citrus' because its
+coerce_mode_error2.m:050:   instantiatedness includes the function symbol
+coerce_mode_error2.m:050:   `nil'.
diff --git a/tests/invalid/coerce_mode_error2.m b/tests/invalid/coerce_mode_error2.m
new file mode 100644
index 000000000..b2c8dfda8
--- /dev/null
+++ b/tests/invalid/coerce_mode_error2.m
@@ -0,0 +1,52 @@
+%---------------------------------------------------------------------------%
+% vim: ts=4 sw=4 et ft=mercury
+%---------------------------------------------------------------------------%
+
+:- module coerce_mode_error2.
+:- interface.
+
+:- type fruit
+    --->    apple
+    ;       orange
+    ;       lemon
+    ;       banana.
+
+:- type citrus =< fruit
+    --->    orange
+    ;       lemon.
+
+:- type foo(T)
+    --->    nil
+    ;       foo(int, T).
+
+:- type foo_citrus =< foo(citrus)
+    --->    foo(int, citrus).
+
+%---------------------------------------------------------------------------%
+
+:- implementation.
+
+:- pred test1(foo_citrus::out) is multi.
+
+test1(Y) :-
+    (
+        X = foo(1, apple)       % apple cannot be converted
+    ;
+        X = foo(2, banana)      % banana cannot be converted
+    ;
+        X = foo(3, orange)
+    ),
+    Y = coerce(X).
+
+:- pred test2(foo_citrus::out) is multi.
+
+test2(Y) :-
+    (
+        X = foo(1, apple)       % apple cannot be converted
+    ;
+        X = nil                 % nil cannot be converted either
+                                % and will be reported preferentially
+    ),
+    Y = coerce(X).
+
+%---------------------------------------------------------------------------%
diff --git a/tests/invalid/coerce_recursive_inst.err_exp b/tests/invalid/coerce_recursive_inst.err_exp
index 7b279c40b..718fc3598 100644
--- a/tests/invalid/coerce_recursive_inst.err_exp
+++ b/tests/invalid/coerce_recursive_inst.err_exp
@@ -2,46 +2,20 @@ coerce_recursive_inst.m:042: In clause for
 coerce_recursive_inst.m:042:   `bad1(in((coerce_recursive_inst.cons))) =
 coerce_recursive_inst.m:042:   out((coerce_recursive_inst.cons))':
 coerce_recursive_inst.m:042:   in coerce:
-coerce_recursive_inst.m:042:   mode error: the input term has instantiatedness
-coerce_recursive_inst.m:042:     named inst cons,
-coerce_recursive_inst.m:042:     which expands to
-coerce_recursive_inst.m:042:       bound(
-coerce_recursive_inst.m:042:         cons(
-coerce_recursive_inst.m:042:           ground,
-coerce_recursive_inst.m:042:           named inst rec,
-coerce_recursive_inst.m:042:           which expands to
-coerce_recursive_inst.m:042:             bound(
-coerce_recursive_inst.m:042:               cons(
-coerce_recursive_inst.m:042:                 ground,
-coerce_recursive_inst.m:042:                 named inst rec
-coerce_recursive_inst.m:042:               )
-coerce_recursive_inst.m:042:             ;
-coerce_recursive_inst.m:042:               nil
-coerce_recursive_inst.m:042:             )
-coerce_recursive_inst.m:042:         )
-coerce_recursive_inst.m:042:       ),
-coerce_recursive_inst.m:042:   and cannot be converted to the type
-coerce_recursive_inst.m:042:   `coerce_recursive_inst.rec(coerce_recursive_inst.citrus)'.
+coerce_recursive_inst.m:042:   mode error: in the input term:
+coerce_recursive_inst.m:042:   in argument 1 of function symbol `cons':
+coerce_recursive_inst.m:042:   cannot convert the subterm from type
+coerce_recursive_inst.m:042:   `coerce_recursive_inst.fruit' to
+coerce_recursive_inst.m:042:   `coerce_recursive_inst.citrus' because it has
+coerce_recursive_inst.m:042:   instantiatedness `ground', and the former type
+coerce_recursive_inst.m:042:   is not a subtype of the latter type.
 coerce_recursive_inst.m:048: In clause for
 coerce_recursive_inst.m:048:   `bad2(in((coerce_recursive_inst.cons))) = out':
 coerce_recursive_inst.m:048:   in coerce:
-coerce_recursive_inst.m:048:   mode error: the input term has instantiatedness
-coerce_recursive_inst.m:048:     named inst cons,
-coerce_recursive_inst.m:048:     which expands to
-coerce_recursive_inst.m:048:       bound(
-coerce_recursive_inst.m:048:         cons(
-coerce_recursive_inst.m:048:           ground,
-coerce_recursive_inst.m:048:           named inst rec,
-coerce_recursive_inst.m:048:           which expands to
-coerce_recursive_inst.m:048:             bound(
-coerce_recursive_inst.m:048:               cons(
-coerce_recursive_inst.m:048:                 ground,
-coerce_recursive_inst.m:048:                 named inst rec
-coerce_recursive_inst.m:048:               )
-coerce_recursive_inst.m:048:             ;
-coerce_recursive_inst.m:048:               nil
-coerce_recursive_inst.m:048:             )
-coerce_recursive_inst.m:048:         )
-coerce_recursive_inst.m:048:       ),
-coerce_recursive_inst.m:048:   and cannot be converted to the type
-coerce_recursive_inst.m:048:   `coerce_recursive_inst.rec(coerce_recursive_inst.citrus)'.
+coerce_recursive_inst.m:048:   mode error: in the input term:
+coerce_recursive_inst.m:048:   in argument 1 of function symbol `cons':
+coerce_recursive_inst.m:048:   cannot convert the subterm from type
+coerce_recursive_inst.m:048:   `coerce_recursive_inst.fruit' to
+coerce_recursive_inst.m:048:   `coerce_recursive_inst.citrus' because it has
+coerce_recursive_inst.m:048:   instantiatedness `ground', and the former type
+coerce_recursive_inst.m:048:   is not a subtype of the latter type.
diff --git a/tests/invalid/coerce_recursive_type.err_exp b/tests/invalid/coerce_recursive_type.err_exp
index 7cb64f357..bd90346aa 100644
--- a/tests/invalid/coerce_recursive_type.err_exp
+++ b/tests/invalid/coerce_recursive_type.err_exp
@@ -1,72 +1,32 @@
 coerce_recursive_type.m:045: In clause for `bad(in) = out':
 coerce_recursive_type.m:045:   in coerce:
-coerce_recursive_type.m:045:   mode error: the input term has instantiatedness
-coerce_recursive_type.m:045:   `ground',
-coerce_recursive_type.m:045:   and cannot be converted to the type
-coerce_recursive_type.m:045:   `coerce_recursive_type.rec(coerce_recursive_type.citrus)'.
+coerce_recursive_type.m:045:   mode error: cannot convert the input term from
+coerce_recursive_type.m:045:   type
+coerce_recursive_type.m:045:   `coerce_recursive_type.rec(coerce_recursive_type.fruit)'
+coerce_recursive_type.m:045:   to
+coerce_recursive_type.m:045:   `coerce_recursive_type.rec(coerce_recursive_type.citrus)'
+coerce_recursive_type.m:045:   because it has instantiatedness `ground', and
+coerce_recursive_type.m:045:   the former type is not a subtype of the latter
+coerce_recursive_type.m:045:   type.
 coerce_recursive_type.m:051: In clause for
 coerce_recursive_type.m:051:   `bad2(in((coerce_recursive_type.cons_apple3))) =
 coerce_recursive_type.m:051:   out':
 coerce_recursive_type.m:051:   in coerce:
-coerce_recursive_type.m:051:   mode error: the input term has instantiatedness
-coerce_recursive_type.m:051:     named inst cons_apple3,
-coerce_recursive_type.m:051:     which expands to
-coerce_recursive_type.m:051:       bound(
-coerce_recursive_type.m:051:         cons(
-coerce_recursive_type.m:051:           ground,
-coerce_recursive_type.m:051:           named inst cons_apple2,
-coerce_recursive_type.m:051:           which expands to
-coerce_recursive_type.m:051:             bound(
-coerce_recursive_type.m:051:               cons(
-coerce_recursive_type.m:051:                 ground,
-coerce_recursive_type.m:051:                 named inst cons_apple,
-coerce_recursive_type.m:051:                 which expands to
-coerce_recursive_type.m:051:                   bound(
-coerce_recursive_type.m:051:                     cons(
-coerce_recursive_type.m:051:                       named inst apple,
-coerce_recursive_type.m:051:                       which expands to
-coerce_recursive_type.m:051:                         bound(
-coerce_recursive_type.m:051:                           apple
-coerce_recursive_type.m:051:                         ),
-coerce_recursive_type.m:051:                         ground
-coerce_recursive_type.m:051:                     )
-coerce_recursive_type.m:051:                   )
-coerce_recursive_type.m:051:               )
-coerce_recursive_type.m:051:             )
-coerce_recursive_type.m:051:         )
-coerce_recursive_type.m:051:       ),
-coerce_recursive_type.m:051:   and cannot be converted to the type
-coerce_recursive_type.m:051:   `coerce_recursive_type.rec(coerce_recursive_type.citrus)'.
+coerce_recursive_type.m:051:   mode error: in the input term:
+coerce_recursive_type.m:051:   in argument 1 of function symbol `cons':
+coerce_recursive_type.m:051:   cannot convert the subterm from type
+coerce_recursive_type.m:051:   `coerce_recursive_type.fruit' to
+coerce_recursive_type.m:051:   `coerce_recursive_type.citrus' because it has
+coerce_recursive_type.m:051:   instantiatedness `ground', and the former type
+coerce_recursive_type.m:051:   is not a subtype of the latter type.
 coerce_recursive_type.m:058: In clause for
 coerce_recursive_type.m:058:   `bad3(in((coerce_recursive_type.cons_apple3))) =
 coerce_recursive_type.m:058:   out':
 coerce_recursive_type.m:058:   in coerce:
-coerce_recursive_type.m:058:   mode error: the input term has instantiatedness
-coerce_recursive_type.m:058:     named inst cons_apple3,
-coerce_recursive_type.m:058:     which expands to
-coerce_recursive_type.m:058:       bound(
-coerce_recursive_type.m:058:         cons(
-coerce_recursive_type.m:058:           ground,
-coerce_recursive_type.m:058:           named inst cons_apple2,
-coerce_recursive_type.m:058:           which expands to
-coerce_recursive_type.m:058:             bound(
-coerce_recursive_type.m:058:               cons(
-coerce_recursive_type.m:058:                 ground,
-coerce_recursive_type.m:058:                 named inst cons_apple,
-coerce_recursive_type.m:058:                 which expands to
-coerce_recursive_type.m:058:                   bound(
-coerce_recursive_type.m:058:                     cons(
-coerce_recursive_type.m:058:                       named inst apple,
-coerce_recursive_type.m:058:                       which expands to
-coerce_recursive_type.m:058:                         bound(
-coerce_recursive_type.m:058:                           apple
-coerce_recursive_type.m:058:                         ),
-coerce_recursive_type.m:058:                         ground
-coerce_recursive_type.m:058:                     )
-coerce_recursive_type.m:058:                   )
-coerce_recursive_type.m:058:               )
-coerce_recursive_type.m:058:             )
-coerce_recursive_type.m:058:         )
-coerce_recursive_type.m:058:       ),
-coerce_recursive_type.m:058:   and cannot be converted to the type
-coerce_recursive_type.m:058:   `coerce_recursive_type.rec(coerce_recursive_type.fruit)'.
+coerce_recursive_type.m:058:   mode error: in the input term:
+coerce_recursive_type.m:058:   in argument 2 of function symbol `cons':
+coerce_recursive_type.m:058:   in argument 2 of function symbol `cons':
+coerce_recursive_type.m:058:   in argument 1 of function symbol `cons':
+coerce_recursive_type.m:058:   the subterm has instantiatedness `bound(apple)',
+coerce_recursive_type.m:058:   which is invalid for the type
+coerce_recursive_type.m:058:   `coerce_recursive_type.citrus'.
diff --git a/tests/invalid/coerce_unreachable.err_exp b/tests/invalid/coerce_unreachable.err_exp
index 559537314..ce04f78a5 100644
--- a/tests/invalid/coerce_unreachable.err_exp
+++ b/tests/invalid/coerce_unreachable.err_exp
@@ -1,7 +1,8 @@
 coerce_unreachable.m:027: In clause for `p1(in, out)':
 coerce_unreachable.m:027:   in coerce:
-coerce_unreachable.m:027:   mode error: the input term has instantiatedness
-coerce_unreachable.m:027:   `ground',
-coerce_unreachable.m:027:   and cannot be converted to the type
-coerce_unreachable.m:027:   `coerce_unreachable.citrus'.
+coerce_unreachable.m:027:   mode error: cannot convert the input term from type
+coerce_unreachable.m:027:   `coerce_unreachable.fruit' to
+coerce_unreachable.m:027:   `coerce_unreachable.citrus' because it has
+coerce_unreachable.m:027:   instantiatedness `ground', and the former type is
+coerce_unreachable.m:027:   not a subtype of the latter type.
 For more information, recompile with `-E'.
-- 
2.30.0



More information about the reviews mailing list