[m-rev.] for review: Require same order for existential type vars in subtype as in supertype.

Peter Wang novalazy at gmail.com
Fri Jul 5 14:16:43 AEST 2024


We require a one-to-one mapping from the existentially quantified type
variables for a subtype constructor to those of the supertype.
However, the order that existentially quantified type variables are
listed in a du constructor definition influences the order of the
type_info and type_class_info arguments in a heap cell, e.g.

   :- type pair1
      --->  some [A, B] pair(A, B).

   :- type pair2 =< pair1
      --->  some [B, A] pair(A, B).

A heap cell representing the term pair(1, "two") of type pair1 would
have type_info arguments in the order [int, string],
whereas a heap cell representing the same term of type pair2 would
have type_info arguments in the opposite order [string, int].

To ensure safe conversion between a subtype and supertype with
existential type arguments, the extra type_info and type_class_info
arguments must appear in the same order in the heap cell.

This change tightens subtype definitions, requiring that the
existentially quantified type variables for a subtype constructor be
listed in the same order as in the supertype constructor definition
(note: the type variable _names_ still do not matter).
This should not be a breaking change in practice.

doc/reference_manual.texi:
    Document the new rule.

    Add a XXX about a dubious language feature.

compiler/add_type.m:
    Check the new rule. Unlike before, we first check the conditions on
    existential type vars and existential class constraints before
    checking the types of constructor arguments.

compiler/prog_data.m:
    Fix a comment.

tests/invalid/subtype_exist_vars.m:
tests/invalid/subtype_exist_vars.err_exp:
    Extend test case.

tests/invalid/subtype_exist_constraints.err_exp:
    Update expected output.

NEWS.md:
    Announce change to language.

diff --git a/NEWS.md b/NEWS.md
index af466fb02..3afebeb83 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1226,6 +1226,10 @@ Changes to the Mercury language
 * You can now use the escape sequence '\e' to refer to the ESC (escape)
   character, just as you can use '\n' to refer to the newline character.
 
+* The order in which existentially quantified type variables are listed
+  for a constructor of a subtype is now significant, and must match
+  the order of the corresponding variables for the supertype constructor.
+
 Changes to the Mercury compiler
 -------------------------------
 
diff --git a/compiler/add_type.m b/compiler/add_type.m
index 3cc665754..cde7f340d 100644
--- a/compiler/add_type.m
+++ b/compiler/add_type.m
@@ -1696,64 +1696,157 @@ check_subtype_ctor(TypeTable, TVarSet, TypeStatus, Ctor, SuperCtor,
     Ctor = ctor(_, MaybeExistConstraints, CtorSymName, Args, Arity, Context),
     SuperCtor = ctor(_, MaybeSuperExistConstraints, _SuperCtorName, SuperArgs,
         _SuperArity, _SuperContext),
-    list.foldl4_corresponding(
-        check_subtype_ctor_arg(TypeTable, TVarSet, TypeStatus,
-            CtorSymName, MaybeExistConstraints, MaybeSuperExistConstraints),
-        Args, SuperArgs,
-        1, _, bimap.init, ExistQVarsMapping,
-        did_not_find_invalid_type, FoundInvalidType, !Specs),
+    CtorSymNameArity = sym_name_arity(CtorSymName, Arity),
+    check_subtype_ctor_exist_constraints(CtorSymNameArity, Context,
+        MaybeExistConstraints, MaybeSuperExistConstraints,
+        MaybeExistQVarsMapping, !Specs),
     (
-        FoundInvalidType = did_not_find_invalid_type,
+        MaybeExistQVarsMapping = yes(ExistQVarsMapping),
+        list.foldl3_corresponding(
+            check_subtype_ctor_arg(TypeTable, TVarSet, TypeStatus,
+                CtorSymName, ExistQVarsMapping),
+            Args, SuperArgs,
+            1, _, did_not_find_invalid_type, FoundInvalidType, !Specs),
         (
-            MaybeExistConstraints = no_exist_constraints,
-            MaybeSuperExistConstraints = no_exist_constraints
+            FoundInvalidType = did_not_find_invalid_type
         ;
-            MaybeExistConstraints = exist_constraints(Constraints),
-            MaybeSuperExistConstraints = exist_constraints(SuperConstraints),
-            CtorSymNameArity = sym_name_arity(CtorSymName, Arity),
-            check_subtype_ctor_exist_constraints(CtorSymNameArity,
-                Constraints, SuperConstraints, ExistQVarsMapping, Context,
-                !FoundInvalidType, !Specs)
-        ;
-            MaybeExistConstraints = no_exist_constraints,
-            MaybeSuperExistConstraints = exist_constraints(_),
-            unexpected($pred, "exist_constraints mismatch")
-        ;
-            MaybeExistConstraints = exist_constraints(_),
-            MaybeSuperExistConstraints = no_exist_constraints,
-            unexpected($pred, "exist_constraints mismatch")
+            FoundInvalidType = found_invalid_type,
+            !:FoundInvalidType = FoundInvalidType
         )
     ;
-        FoundInvalidType = found_invalid_type,
-        !:FoundInvalidType = FoundInvalidType
+        MaybeExistQVarsMapping = no,
+        !:FoundInvalidType = found_invalid_type
     ).
 
 %---------------------%
 
-    % A map from existential type variable in the supertype constructor
+    % A map from an existential type variable in the supertype constructor
     % to an existential type variable in the subtype constructor.
     %
 :- type existq_tvar_mapping == bimap(tvar, tvar).
 
-:- pred check_subtype_ctor_arg(type_table::in, tvarset::in, type_status::in,
-    sym_name::in,
+:- pred check_subtype_ctor_exist_constraints(sym_name_arity::in,
+    prog_context::in,
     maybe_cons_exist_constraints::in, maybe_cons_exist_constraints::in,
-    constructor_arg::in, constructor_arg::in,
-    int::in, int::out, existq_tvar_mapping::in, existq_tvar_mapping::out,
+    maybe(existq_tvar_mapping)::out,
+    list(error_spec)::in, list(error_spec)::out) is det.
+
+check_subtype_ctor_exist_constraints(CtorSymNameArity, Context,
+        MaybeExistConstraints, MaybeSuperExistConstraints, Result, !Specs) :-
+    (
+        MaybeExistConstraints = no_exist_constraints,
+        ExistQVars = [],
+        Constraints = []
+    ;
+        MaybeExistConstraints = exist_constraints(ExistConstraints),
+        ExistConstraints = cons_exist_constraints(ExistQVars, Constraints, _, _)
+    ),
+    (
+        MaybeSuperExistConstraints = no_exist_constraints,
+        SuperExistQVars = [],
+        SuperConstraints = []
+    ;
+        MaybeSuperExistConstraints = exist_constraints(SuperExistConstraints),
+        SuperExistConstraints = cons_exist_constraints(SuperExistQVars,
+            SuperConstraints, _, _)
+    ),
+    list.length(ExistQVars, NumExistQVars),
+    list.length(SuperExistQVars, NumSuperExistQVars),
+    ( if NumExistQVars = NumSuperExistQVars then
+        ( if
+            list.foldl_corresponding(build_existq_tvars_mapping,
+                ExistQVars, SuperExistQVars, bimap.init, ExistQVarsMapping)
+        then
+            check_subtype_ctor_exist_constraints(CtorSymNameArity, Context,
+                ExistQVarsMapping, Constraints, SuperConstraints,
+                ConstraintsOk, !Specs),
+            (
+                ConstraintsOk = yes,
+                Result = yes(ExistQVarsMapping)
+            ;
+                ConstraintsOk = no,
+                Result = no
+            )
+        else
+            Pieces =
+                [words("Error: existentially quantified type variables"),
+                words("for")] ++
+                color_as_subject([unqual_sym_name_arity(CtorSymNameArity)]) ++
+                color_as_incorrect([words("do not correspond")]) ++
+                [words("one-to-one in the subtype and supertype."), nl],
+            Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces),
+            !:Specs = [Spec | !.Specs],
+            Result = no
+        )
+    else
+        Pieces = [words("Error:")] ++
+            color_as_subject([unqual_sym_name_arity(CtorSymNameArity)]) ++
+            [words("has wrong number of"),
+            words("existentially quantified type variables"), prefix("(")] ++
+            color_as_incorrect([int_fixed(NumExistQVars)]) ++
+            [suffix(","), words("expected")] ++
+            color_as_correct([int_fixed(NumSuperExistQVars)]) ++
+            [suffix(")."), nl],
+        Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces),
+        !:Specs = [Spec | !.Specs],
+        Result = no
+    ).
+
+:- pred build_existq_tvars_mapping(tvar::in, tvar::in,
+    existq_tvar_mapping::in, existq_tvar_mapping::out) is semidet.
+
+build_existq_tvars_mapping(VarA, VarB, !ExistQVarsMapping) :-
+    ( if bimap.insert(VarB, VarA, !ExistQVarsMapping) then
+        true
+    else
+        % The reference manual does not require distinct type variables in a
+        % existential quantifier list of a constructor definition (whether or
+        % not a du type is a subtype). This might be an oversight.
+        % For now, this allows duplicate variables in subtype constructor
+        % definitions as well.
+        bimap.forward_search(!.ExistQVarsMapping, VarB, VarA)
+    ).
+
+:- pred check_subtype_ctor_exist_constraints(sym_name_arity::in,
+    prog_context::in, existq_tvar_mapping::in,
+    list(prog_constraint)::in, list(prog_constraint)::in, bool::out,
+    list(error_spec)::in, list(error_spec)::out) is det.
+
+check_subtype_ctor_exist_constraints(CtorSymNameArity, Context,
+        ExistQVarsMapping, Constraints, SuperConstraints0, Result, !Specs) :-
+    ExistQVarsRenaming = bimap.forward_map(ExistQVarsMapping),
+    apply_variable_renaming_to_prog_constraint_list(ExistQVarsRenaming,
+        SuperConstraints0, SuperConstraints),
+    list.sort(Constraints, SortedConstraints),
+    list.sort(SuperConstraints, SortedSuperConstraints),
+    ( if SortedConstraints = SortedSuperConstraints then
+        Result = yes
+    else
+        Pieces = [words("Error: existential class constraints for")] ++
+            color_as_subject([unqual_sym_name_arity(CtorSymNameArity)]) ++
+            color_as_incorrect([words("differ")]) ++
+            [words("in the subtype and supertype."), nl],
+        Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces),
+        !:Specs = [Spec | !.Specs],
+        Result = no
+    ).
+
+%---------------------%
+
+:- pred check_subtype_ctor_arg(type_table::in, tvarset::in, type_status::in,
+    sym_name::in, existq_tvar_mapping::in,
+    constructor_arg::in, constructor_arg::in, int::in, int::out,
     found_invalid_type::in, found_invalid_type::out,
     list(error_spec)::in, list(error_spec)::out) is det.
 
 check_subtype_ctor_arg(TypeTable, TVarSet, OrigTypeStatus, CtorSymName,
-        MaybeExistConstraints, MaybeSuperExistConstraints,
-        CtorArg, SuperCtorArg,
-        ArgNum, ArgNum + 1, !ExistQVarsMapping, !FoundInvalidType, !Specs) :-
+        ExistQVarsMapping, CtorArg, SuperCtorArg, ArgNum, ArgNum + 1,
+        !FoundInvalidType, !Specs) :-
     CtorArg = ctor_arg(_FieldName, ArgType, Context),
     SuperCtorArg = ctor_arg(_SuperFieldName, SuperArgType, _SuperContext),
     ( if
-        check_is_subtype(TypeTable, TVarSet, OrigTypeStatus,
-            ArgType, SuperArgType,
-            MaybeExistConstraints, MaybeSuperExistConstraints,
-            !ExistQVarsMapping)
+        check_is_subtype(TypeTable, TVarSet, OrigTypeStatus, ExistQVarsMapping,
+            ArgType, SuperArgType)
     then
         true
     else
@@ -1779,12 +1872,10 @@ check_subtype_ctor_arg(TypeTable, TVarSet, OrigTypeStatus, CtorSymName,
 %---------------------%
 
 :- pred check_is_subtype(type_table::in, tvarset::in, type_status::in,
-    mer_type::in, mer_type::in,
-    maybe_cons_exist_constraints::in, maybe_cons_exist_constraints::in,
-    existq_tvar_mapping::in, existq_tvar_mapping::out) is semidet.
+    existq_tvar_mapping::in, mer_type::in, mer_type::in) is semidet.
 
-check_is_subtype(TypeTable, TVarSet0, OrigTypeStatus, TypeA, TypeB,
-        MaybeExistConstraintsA, MaybeExistConstraintsB, !ExistQVarsMapping) :-
+check_is_subtype(TypeTable, TVarSet0, OrigTypeStatus, ExistQVarsMapping,
+        TypeA, TypeB) :-
     require_complete_switch [TypeA]
     (
         TypeA = builtin_type(BuiltinType),
@@ -1792,8 +1883,7 @@ check_is_subtype(TypeTable, TVarSet0, OrigTypeStatus, TypeA, TypeB,
     ;
         TypeA = type_variable(VarA, Kind),
         TypeB = type_variable(VarB, Kind),
-        check_is_subtype_var_var(VarA, VarB,
-            MaybeExistConstraintsA, MaybeExistConstraintsB, !ExistQVarsMapping)
+        check_is_subtype_var_var(ExistQVarsMapping, VarA, VarB)
     ;
         TypeA = defined_type(NameA, ArgTypesA, Kind),
         TypeB = defined_type(NameB, ArgTypesB, Kind),
@@ -1806,9 +1896,7 @@ check_is_subtype(TypeTable, TVarSet0, OrigTypeStatus, TypeA, TypeB,
             % TypeA and TypeB have the same type constructor.
             % Check their corresponding argument types.
             check_corresponding_args_are_subtype(TypeTable, TVarSet0,
-                OrigTypeStatus, ArgTypesA, ArgTypesB,
-                MaybeExistConstraintsA, MaybeExistConstraintsB,
-                !ExistQVarsMapping)
+                OrigTypeStatus, ExistQVarsMapping, ArgTypesA, ArgTypesB)
         else
             % TypeA and TypeB have different type constructors.
             % Find a subtype definition s(S1, ..., Sn) =< t(T1, ..., Tk)
@@ -1839,9 +1927,7 @@ check_is_subtype(TypeTable, TVarSet0, OrigTypeStatus, TypeA, TypeB,
 
             % Check that t(T1', ..., Tk') =< TypeB.
             check_is_subtype(TypeTable, TVarSet, OrigTypeStatus,
-                RenamedSuperTypeA, TypeB,
-                MaybeExistConstraintsA, MaybeExistConstraintsB,
-                !ExistQVarsMapping)
+                ExistQVarsMapping, RenamedSuperTypeA, TypeB)
         )
     ;
         TypeA = tuple_type(ArgTypesA, Kind),
@@ -1849,8 +1935,7 @@ check_is_subtype(TypeTable, TVarSet0, OrigTypeStatus, TypeA, TypeB,
         list.length(ArgTypesA, Arity),
         list.length(ArgTypesB, Arity),
         check_corresponding_args_are_subtype(TypeTable, TVarSet0,
-            OrigTypeStatus, ArgTypesA, ArgTypesB,
-            MaybeExistConstraintsA, MaybeExistConstraintsB, !ExistQVarsMapping)
+            OrigTypeStatus, ExistQVarsMapping, ArgTypesA, ArgTypesB)
     ;
         TypeA = higher_order_type(PredOrFunc, ArgTypesA, HOInstInfoA, Purity),
         TypeB = higher_order_type(PredOrFunc, ArgTypesB, HOInstInfoB, Purity),
@@ -1872,94 +1957,51 @@ check_is_subtype(TypeTable, TVarSet0, OrigTypeStatus, TypeA, TypeB,
             MaybeArgModesB = no
         ),
         check_is_subtype_higher_order(TypeTable, TVarSet0, OrigTypeStatus,
-            ArgTypesA, ArgTypesB, MaybeArgModesA, MaybeArgModesB,
-            MaybeExistConstraintsA, MaybeExistConstraintsB, !ExistQVarsMapping)
+            ExistQVarsMapping, ArgTypesA, ArgTypesB,
+            MaybeArgModesA, MaybeArgModesB)
     ;
         TypeA = apply_n_type(_, _, _),
         fail
     ;
         TypeA = kinded_type(TypeA1, Kind),
         TypeB = kinded_type(TypeB1, Kind),
-        check_is_subtype(TypeTable, TVarSet0, OrigTypeStatus, TypeA1, TypeB1,
-            MaybeExistConstraintsA, MaybeExistConstraintsB, !ExistQVarsMapping)
+        check_is_subtype(TypeTable, TVarSet0, OrigTypeStatus,
+            ExistQVarsMapping, TypeA1, TypeB1)
     ).
 
-:- pred check_is_subtype_var_var(tvar::in, tvar::in,
-    maybe_cons_exist_constraints::in, maybe_cons_exist_constraints::in,
-    existq_tvar_mapping::in, existq_tvar_mapping::out) is semidet.
-
-check_is_subtype_var_var(VarA, VarB,
-        MaybeExistConstraintsA, MaybeExistConstraintsB, !ExistQVarsMapping) :-
-    ( if VarA = VarB then
-        % Double check that VarA (and VarB) are universally quantified.
-        is_univ_quantified(MaybeExistConstraintsA, VarA)
-    else
-        % VarA and VarB should both be existentially quantified.
-        % There must be a one-to-one correspondence between existentially
-        % quantified type variables in the subtype and the supertype.
-        ( if bimap.forward_search(!.ExistQVarsMapping, VarB, PrevVar) then
-            % VarB was seen already; it should be mapped to VarA.
-            PrevVar = VarA
-        else
-            % VarB was not seen yet.
-            MaybeExistConstraintsA = exist_constraints(ExistConstraintsA),
-            MaybeExistConstraintsB = exist_constraints(ExistConstraintsB),
-            ExistConstraintsA = cons_exist_constraints(_ExistQVarsA,
-                _ConstraintsA, UnconstrainedExistQVarsA, ConstrainedExistQVarsA),
-            ExistConstraintsB = cons_exist_constraints(_ExistQVarsB,
-                _ConstraintsB, UnconstrainedExistQVarsB, ConstrainedExistQVarsB),
-            % VarA and VarB should both be unconstrained, or both be
-            % constrained.
-            (
-                list.contains(UnconstrainedExistQVarsA, VarA),
-                list.contains(UnconstrainedExistQVarsB, VarB)
-            ;
-                list.contains(ConstrainedExistQVarsA, VarA),
-                list.contains(ConstrainedExistQVarsB, VarB)
-            ),
-            % Record the correspondence between VarB and VarA.
-            % This fails if another type variable was mapped to VarA already.
-            bimap.insert(VarB, VarA, !ExistQVarsMapping)
-        )
-    ).
-
-:- pred is_univ_quantified(maybe_cons_exist_constraints::in, tvar::in)
+:- pred check_is_subtype_var_var(existq_tvar_mapping::in, tvar::in, tvar::in)
     is semidet.
 
-is_univ_quantified(MaybeExistConstraints, TVar) :-
-    (
-        MaybeExistConstraints = no_exist_constraints
-    ;
-        MaybeExistConstraints = exist_constraints(ExistConstraints),
-        ExistQVars = ExistConstraints ^ cons_existq_tvars,
-        not list.contains(ExistQVars, TVar)
+check_is_subtype_var_var(ExistQVarsMapping, VarA, VarB) :-
+    ( if VarA = VarB then
+        % Double check that VarA and VarB are universally quantified.
+        not bimap.forward_search(ExistQVarsMapping, VarB, _)
+    else
+        % Check that VarA and VarB are corresponding existentially quantified
+        % type variables.
+        bimap.forward_search(ExistQVarsMapping, VarB, VarA)
     ).
 
 :- pred check_corresponding_args_are_subtype(type_table::in, tvarset::in,
-    type_status::in, list(mer_type)::in, list(mer_type)::in,
-    maybe_cons_exist_constraints::in, maybe_cons_exist_constraints::in,
-    existq_tvar_mapping::in, existq_tvar_mapping::out) is semidet.
+    type_status::in, existq_tvar_mapping::in,
+    list(mer_type)::in, list(mer_type)::in) is semidet.
 
 check_corresponding_args_are_subtype(_TypeTable, _TVarSet, _OrigTypeStatus,
-        [], [],
-        _MaybeExistConstraintsA, _MaybeExistConstraintsB, !ExistQVarsMapping).
+        _ExistQVarsMapping, [], []).
 check_corresponding_args_are_subtype(TypeTable, TVarSet, OrigTypeStatus,
-        [TypeA | TypesA], [TypeB | TypesB],
-        MaybeExistConstraintsA, MaybeExistConstraintsB, !ExistQVarsMapping) :-
-    check_is_subtype(TypeTable, TVarSet, OrigTypeStatus, TypeA, TypeB,
-        MaybeExistConstraintsA, MaybeExistConstraintsB, !ExistQVarsMapping),
+        ExistQVarsMapping, [TypeA | TypesA], [TypeB | TypesB]) :-
+    check_is_subtype(TypeTable, TVarSet, OrigTypeStatus, ExistQVarsMapping,
+        TypeA, TypeB),
     check_corresponding_args_are_subtype(TypeTable, TVarSet, OrigTypeStatus,
-        TypesA, TypesB, MaybeExistConstraintsA, MaybeExistConstraintsB,
-        !ExistQVarsMapping).
+        ExistQVarsMapping, TypesA, TypesB).
 
 :- pred check_is_subtype_higher_order(type_table::in, tvarset::in,
-    type_status::in, list(mer_type)::in, list(mer_type)::in,
-    maybe(list(mer_mode))::in, maybe(list(mer_mode))::in,
-    maybe_cons_exist_constraints::in, maybe_cons_exist_constraints::in,
-    existq_tvar_mapping::in, existq_tvar_mapping::out) is semidet.
+    type_status::in, existq_tvar_mapping::in,
+    list(mer_type)::in, list(mer_type)::in,
+    maybe(list(mer_mode))::in, maybe(list(mer_mode))::in) is semidet.
 
 check_is_subtype_higher_order(_TypeTable, _TVarSet, _OrigTypeStatus,
-        [], [], MaybeModesA, MaybeModesB, _, _, !ExistQVarsMapping) :-
+        _ExistQVarsMapping, [], [], MaybeModesA, MaybeModesB) :-
     (
         MaybeModesA = no,
         MaybeModesB = no
@@ -1968,14 +2010,14 @@ check_is_subtype_higher_order(_TypeTable, _TVarSet, _OrigTypeStatus,
         MaybeModesB = yes([])
     ).
 check_is_subtype_higher_order(TypeTable, TVarSet, OrigTypeStatus,
-        [TypeA | TypesA], [TypeB | TypesB], MaybeModesA0, MaybeModesB0,
-        MaybeExistConstraintsA, MaybeExistConstraintsB, !ExistQVarsMapping) :-
+        ExistQVarsMapping,
+        [TypeA | TypesA], [TypeB | TypesB], MaybeModesA0, MaybeModesB0) :-
     % Check arguments of higher order term have the same type.
     % This could be more efficient, but should be rarely used anyway.
-    check_is_subtype(TypeTable, TVarSet, OrigTypeStatus, TypeA, TypeB,
-        MaybeExistConstraintsA, MaybeExistConstraintsB, !ExistQVarsMapping),
-    check_is_subtype(TypeTable, TVarSet, OrigTypeStatus, TypeB, TypeA,
-        MaybeExistConstraintsA, MaybeExistConstraintsB, !ExistQVarsMapping),
+    check_is_subtype(TypeTable, TVarSet, OrigTypeStatus, ExistQVarsMapping,
+        TypeA, TypeB),
+    check_is_subtype(TypeTable, TVarSet, OrigTypeStatus, ExistQVarsMapping,
+        TypeB, TypeA),
 
     % Argument modes, if available, must match exactly.
     (
@@ -1993,38 +2035,7 @@ check_is_subtype_higher_order(TypeTable, TVarSet, OrigTypeStatus,
     ),
 
     check_is_subtype_higher_order(TypeTable, TVarSet, OrigTypeStatus,
-        TypesA, TypesB, MaybeModesA, MaybeModesB,
-        MaybeExistConstraintsA, MaybeExistConstraintsB, !ExistQVarsMapping).
-
-%---------------------%
-
-:- pred check_subtype_ctor_exist_constraints(sym_name_arity::in,
-    cons_exist_constraints::in, cons_exist_constraints::in,
-    existq_tvar_mapping::in, prog_context::in,
-    found_invalid_type::in, found_invalid_type::out,
-    list(error_spec)::in, list(error_spec)::out) is det.
-
-check_subtype_ctor_exist_constraints(CtorSymNameArity,
-        ExistConstraints, SuperExistConstraints, ExistQVarsMapping, Context,
-        !FoundInvalidType, !Specs) :-
-    ExistConstraints = cons_exist_constraints(_, Constraints, _, _),
-    SuperExistConstraints = cons_exist_constraints(_, SuperConstraints0, _, _),
-    ExistQVarsRenaming = bimap.forward_map(ExistQVarsMapping),
-    apply_variable_renaming_to_prog_constraint_list(ExistQVarsRenaming,
-        SuperConstraints0, SuperConstraints),
-    list.sort(Constraints, SortedConstraints),
-    list.sort(SuperConstraints, SortedSuperConstraints),
-    ( if SortedConstraints = SortedSuperConstraints then
-        true
-    else
-        Pieces = [words("Error: existential class constraints for")] ++
-            color_as_subject([unqual_sym_name_arity(CtorSymNameArity)]) ++
-            color_as_incorrect([words("differ")]) ++
-            [words("in the subtype and supertype."), nl],
-        Spec = spec($pred, severity_error, phase_pt2h, Context, Pieces),
-        !:Specs = [Spec | !.Specs],
-        !:FoundInvalidType = found_invalid_type
-    ).
+        ExistQVarsMapping, TypesA, TypesB, MaybeModesA, MaybeModesB).
 
 %---------------------%
 
diff --git a/compiler/prog_data.m b/compiler/prog_data.m
index 0d7652eaf..2d007c58e 100644
--- a/compiler/prog_data.m
+++ b/compiler/prog_data.m
@@ -2,7 +2,7 @@
 % vim: ft=mercury ts=4 sw=4 et
 %---------------------------------------------------------------------------%
 % Copyright (C) 1996-2012 The University of Melbourne.
-% Copyright (C) 2014-2022 The Mercury team.
+% Copyright (C) 2014-2024 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.
 %---------------------------------------------------------------------------%
@@ -514,7 +514,7 @@ cons_id_is_const_struct(ConsId, ConstNum) :-
 
 :- type cons_exist_constraints
     --->    cons_exist_constraints(
-                % Neither list may be empty.
+                % cons_existq_tvars cannot be empty.
                 cons_existq_tvars   :: existq_tvars,
                 cons_constraints    :: list(prog_constraint),
 
diff --git a/doc/reference_manual.texi b/doc/reference_manual.texi
index bf3ac3f4d..d7ceaa281 100644
--- a/doc/reference_manual.texi
+++ b/doc/reference_manual.texi
@@ -3386,13 +3386,21 @@ And this is an example of the second:
 
 (There are more examples below.)
 
-If the subtype retains a constructor from the super type
+If the subtype retains a constructor from the supertype
 that has one or more existentially quantified type variables,
-then there must be a one-to-one mapping between
-the existentially quantified type variables
-for the constructor in the subtype definition
-and the same constructor in the supertype definition.
-The subtype constructor must repeat
+then the subtype constructor must also have
+an existential type quantifier (i.e.@ @code{some})
+with a list of type variables,
+of the same length as that of the supertype constructor.
+ at c XXX we don't require DISTINCT type variables because non-subtype du type
+ at c definitions also allow duplicate type variables. That might be an oversight,
+ at c as the compiler will currently abort somewhere in the backend.
+The order of the variables
+in the two lists of existentially quantified type variables
+is significant:
+corresponding type variables in the two lists map to each other,
+and this mapping must be one-to-one.
+The subtype constructor must also repeat
 exactly the same set of existential class constraints
 on its existentially quantified type variables as in the supertype;
 the subtype constructor cannot add or remove any existential class constraints.
diff --git a/tests/invalid/subtype_exist_constraints.err_exp b/tests/invalid/subtype_exist_constraints.err_exp
index 1334dd2d9..89d5a56a2 100644
--- a/tests/invalid/subtype_exist_constraints.err_exp
+++ b/tests/invalid/subtype_exist_constraints.err_exp
@@ -1,6 +1,5 @@
-subtype_exist_constraints.m:020: Error: the first argument of `lemon' has a
-subtype_exist_constraints.m:020:   type, `L', which is not a subtype of the
-subtype_exist_constraints.m:020:   corresponding argument type `L' in the
+subtype_exist_constraints.m:020: Error: existential class constraints for
+subtype_exist_constraints.m:020:   `lemon'/1 differ in the subtype and
 subtype_exist_constraints.m:020:   supertype.
 subtype_exist_constraints.m:023: Error: existential class constraints for
 subtype_exist_constraints.m:023:   `lemon'/1 differ in the subtype and
diff --git a/tests/invalid/subtype_exist_vars.err_exp b/tests/invalid/subtype_exist_vars.err_exp
index 562efaf92..3cd7bd438 100644
--- a/tests/invalid/subtype_exist_vars.err_exp
+++ b/tests/invalid/subtype_exist_vars.err_exp
@@ -1,15 +1,25 @@
-subtype_exist_vars.m:020: Error: the first argument of `orange' has a type,
-subtype_exist_vars.m:020:   `T', which is not a subtype of the corresponding
-subtype_exist_vars.m:020:   argument type `O' in the supertype.
-subtype_exist_vars.m:023: Error: the first argument of `apple' has a type, `T',
-subtype_exist_vars.m:023:   which is not a subtype of the corresponding
-subtype_exist_vars.m:023:   argument type `int' in the supertype.
-subtype_exist_vars.m:029: Error: the second argument of `lemon' has a type,
-subtype_exist_vars.m:029:   `B', which is not a subtype of the corresponding
-subtype_exist_vars.m:029:   argument type `T' in the supertype.
-subtype_exist_vars.m:032: Error: the second argument of `lemon' has a type,
-subtype_exist_vars.m:032:   `A', which is not a subtype of the corresponding
-subtype_exist_vars.m:032:   argument type `T' in the supertype.
-subtype_exist_vars.m:035: Error: the second argument of `pear' has a type, `A',
-subtype_exist_vars.m:035:   which is not a subtype of the corresponding
-subtype_exist_vars.m:035:   argument type `U' in the supertype.
+subtype_exist_vars.m:021: Error: `orange'/1 has wrong number of existentially
+subtype_exist_vars.m:021:   quantified type variables (0, expected 1).
+subtype_exist_vars.m:024: Error: `apple'/1 has wrong number of existentially
+subtype_exist_vars.m:024:   quantified type variables (1, expected 0).
+subtype_exist_vars.m:030: Error: the second argument of `lemon' has a type,
+subtype_exist_vars.m:030:   `B', which is not a subtype of the corresponding
+subtype_exist_vars.m:030:   argument type `T' in the supertype.
+subtype_exist_vars.m:033: Error: the first argument of `lemon' has a type, `B',
+subtype_exist_vars.m:033:   which is not a subtype of the corresponding
+subtype_exist_vars.m:033:   argument type `T' in the supertype.
+subtype_exist_vars.m:033: Error: the third argument of `lemon' has a type,
+subtype_exist_vars.m:033:   `subtype_exist_vars.foo(A)', which is not a subtype
+subtype_exist_vars.m:033:   of the corresponding argument type
+subtype_exist_vars.m:033:   `subtype_exist_vars.foo(U)' in the supertype.
+subtype_exist_vars.m:036: Error: `pear'/2 has wrong number of existentially
+subtype_exist_vars.m:036:   quantified type variables (1, expected 2).
+subtype_exist_vars.m:039: Error: the first argument of `pear' has a type, `B',
+subtype_exist_vars.m:039:   which is not a subtype of the corresponding
+subtype_exist_vars.m:039:   argument type `T' in the supertype.
+subtype_exist_vars.m:039: Error: the second argument of `pear' has a type, `A',
+subtype_exist_vars.m:039:   which is not a subtype of the corresponding
+subtype_exist_vars.m:039:   argument type `U' in the supertype.
+subtype_exist_vars.m:045: Error: existentially quantified type variables for
+subtype_exist_vars.m:045:   `nondistinct'/2 do not correspond one-to-one in the
+subtype_exist_vars.m:045:   subtype and supertype.
diff --git a/tests/invalid/subtype_exist_vars.m b/tests/invalid/subtype_exist_vars.m
index 0f0084a0e..adf93a545 100644
--- a/tests/invalid/subtype_exist_vars.m
+++ b/tests/invalid/subtype_exist_vars.m
@@ -11,7 +11,8 @@
     --->    apple(A)
     ;       some [O] orange(O)
     ;       some [T, U] lemon(T, T, foo(U))
-    ;       some [T, U] pear(T, U).
+    ;       some [T, U] pear(T, U)
+    ;       some [T, T] nondistinct(T, T). % weird
 
 :- type foo(T)
     --->    foo(T).
@@ -33,3 +34,12 @@
 
 :- type exist_vars_mismatch3 =< fruit
     --->    some [A] pear(A, A).
+
+:- type exist_vars_mismatch4 =< fruit
+    --->    some [A, B] pear(B, A). % order of quantified vars is significant
+
+:- type nondistinct_allowed =< fruit
+    --->    some [A, A] nondistinct(A, A).
+
+:- type nondistinct_mismatch =< fruit
+    --->    some [A, B] nondistinct(A, B).
-- 
2.44.0



More information about the reviews mailing list