diff --git a/NEWS b/NEWS
index 4508e198d..16631a759 100644
--- a/NEWS
+++ b/NEWS
@@ -10,7 +10,9 @@ Changes to the Mercury language:
declarations and in the head of a clause for a user-defined field access
function.
* We now support impure higher-order code.
-* We now allow user-defined comparison routines.
+* We now allow user-defined comparison predicates.
+* User-defined equality and comparison predicates for a type are now
+ required to be defined in the same module as the type.
Changes to the Mercury compiler:
* Better support for incremental program development:
@@ -42,12 +44,15 @@ DETAILED LISTING
Changes to the Mercury language:
-* We now allow user-defined comparison routines, using the syntax
+* We now allow user-defined comparison predicates, using the syntax
:- type t ---> t where equality is unify_t, comparison is compare_t.
See the "User-defined equality and comparison" chapter of the
Mercury Language Reference Manual for details.
+* User-defined equality and comparison predicates for a type are now
+ required to be defined in the same module as the type.
+
* Infix `.' is now accepted as a module name separator. Hence it is
now possible to write io.write_string and list.member to mean the
same thing as io__write_string and list__member, for instance. This
diff --git a/compiler/hlds_out.m b/compiler/hlds_out.m
index 24bf9a722..98a2cc875 100644
--- a/compiler/hlds_out.m
+++ b/compiler/hlds_out.m
@@ -1024,6 +1024,7 @@ hlds_out__marker_name(naive, "naive").
hlds_out__marker_name(psn, "psn").
hlds_out__marker_name(supp_magic, "supp_magic").
hlds_out__marker_name(context, "context").
+hlds_out__marker_name(calls_are_fully_qualified, "calls_are_fully_qualified").
hlds_out__marker_name(not_accessible_by_unqualifed_name,
"not_accessible_by_unqualifed_name").
hlds_out__marker_name(not_accessible_by_partially_qualified_names,
diff --git a/compiler/hlds_pred.m b/compiler/hlds_pred.m
index 1c496582d..6cd7bf83c 100644
--- a/compiler/hlds_pred.m
+++ b/compiler/hlds_pred.m
@@ -373,7 +373,7 @@
% always fully qualified. For calls occurring in `.opt' files
% this will return `is_fully_qualified', otherwise
% `may_be_partially_qualified'.
-:- func calls_are_fully_qualified(import_status) = is_fully_qualified.
+:- func calls_are_fully_qualified(pred_markers) = is_fully_qualified.
% Predicates can be marked with various boolean flags, called
% "markers".
@@ -508,6 +508,12 @@
% If the compiler cannot guarantee termination
% then it must give an error message.
+ ; calls_are_fully_qualified
+ % All calls in this predicate are
+ % fully qualified. This occurs for
+ % predicates read from `.opt' files
+ % and compiler-generated predicates.
+
; not_accessible_by_unqualifed_name
% This predicate is not accessible by its
% unqualified name.
@@ -969,8 +975,8 @@ status_defined_in_this_module(pseudo_exported, yes).
status_defined_in_this_module(exported_to_submodules, yes).
status_defined_in_this_module(local, yes).
-calls_are_fully_qualified(Status) =
- ( Status = opt_imported ->
+calls_are_fully_qualified(Markers) =
+ ( check_marker(Markers, calls_are_fully_qualified) ->
is_fully_qualified
; may_be_partially_qualified
).
diff --git a/compiler/intermod.m b/compiler/intermod.m
index 5f3809a61..1c99c697e 100644
--- a/compiler/intermod.m
+++ b/compiler/intermod.m
@@ -922,7 +922,8 @@ intermod__qualify_instance_method(ModuleInfo,
;
InstanceMethodDefn0 = name(InstanceMethodName0),
PredOrFunc = predicate,
- typecheck__resolve_pred_overloading(ModuleInfo, local,
+ init_markers(Markers),
+ typecheck__resolve_pred_overloading(ModuleInfo, Markers,
MethodCallArgTypes, MethodCallTVarSet,
InstanceMethodName0, InstanceMethodName, PredId),
PredIds = [PredId | PredIds0],
@@ -1123,7 +1124,9 @@ intermod__resolve_user_special_pred_overloading(ModuleInfo, SpecialId,
map__lookup(SpecialPreds, SpecialId - TypeCtor, UnifyPredId),
module_info_pred_info(ModuleInfo, UnifyPredId, UnifyPredInfo),
pred_info_arg_types(UnifyPredInfo, TVarSet, _, ArgTypes),
- typecheck__resolve_pred_overloading(ModuleInfo, local, ArgTypes,
+ init_markers(Markers0),
+ add_marker(Markers0, calls_are_fully_qualified, Markers),
+ typecheck__resolve_pred_overloading(ModuleInfo, Markers, ArgTypes,
TVarSet, Pred0, Pred, UserEqPredId),
intermod__add_proc(UserEqPredId, _, Info0, Info).
@@ -1837,6 +1840,7 @@ intermod__should_output_marker(check_termination, no).
intermod__should_output_marker(generate_inline, _) :-
% This marker should only occur after the magic sets transformation.
error("intermod__should_output_marker: generate_inline").
+intermod__should_output_marker(calls_are_fully_qualified, no).
intermod__should_output_marker(not_accessible_by_unqualifed_name, no).
intermod__should_output_marker(not_accessible_by_partially_qualified_names, no).
diff --git a/compiler/make_hlds.m b/compiler/make_hlds.m
index a53c25e69..338649b18 100644
--- a/compiler/make_hlds.m
+++ b/compiler/make_hlds.m
@@ -1310,7 +1310,8 @@ add_pragma_type_spec_2(Pragma0, Context, PredId,
Clauses = clauses_info(ArgVarSet, VarTypes0, TVarNameMap,
VarTypes0, Args, [Clause], TI_VarMap, TCI_VarMap,
HasForeignClauses),
- pred_info_get_markers(PredInfo0, Markers),
+ pred_info_get_markers(PredInfo0, Markers0),
+ add_marker(Markers0, calls_are_fully_qualified, Markers),
map__init(Proofs),
( pred_info_is_imported(PredInfo0) ->
@@ -3837,7 +3838,10 @@ add_special_pred_for_real(SpecialPredId,
),
unify_proc__generate_clause_info(SpecialPredId, Type, TypeBody,
Context, Module1, ClausesInfo),
- pred_info_set_clauses_info(PredInfo1, ClausesInfo, PredInfo),
+ pred_info_set_clauses_info(PredInfo1, ClausesInfo, PredInfo2),
+ pred_info_get_markers(PredInfo2, Markers2),
+ add_marker(Markers2, calls_are_fully_qualified, Markers),
+ pred_info_set_markers(PredInfo2, Markers, PredInfo),
map__det_update(Preds0, PredId, PredInfo, Preds),
module_info_set_preds(Module1, Preds, Module).
@@ -4274,7 +4278,11 @@ module_add_clause(ModuleInfo0, ClauseVarSet, PredOrFunc, PredName, Args0, Body,
% opt_imported preds are initially tagged as imported and are
% tagged as opt_imported only if/when we see a clause for them
{ Status = opt_imported ->
- pred_info_set_import_status(PredInfo0, opt_imported, PredInfo1)
+ pred_info_set_import_status(PredInfo0,
+ opt_imported, PredInfo0a),
+ pred_info_get_markers(PredInfo0a, Markers0),
+ add_marker(Markers0, calls_are_fully_qualified, Markers1),
+ pred_info_set_markers(PredInfo0a, Markers1, PredInfo1)
;
PredInfo1 = PredInfo0
},
@@ -4375,8 +4383,8 @@ module_add_clause(ModuleInfo0, ClauseVarSet, PredOrFunc, PredName, Args0, Body,
%
pred_info_all_procids(PredInfo6, ProcIds),
( ProcIds = [] ->
- pred_info_get_markers(PredInfo6, Markers0),
- add_marker(Markers0, infer_modes, Markers),
+ pred_info_get_markers(PredInfo6, Markers6),
+ add_marker(Markers6, infer_modes, Markers),
pred_info_set_markers(PredInfo6, Markers, PredInfo)
;
PredInfo = PredInfo6
diff --git a/compiler/post_typecheck.m b/compiler/post_typecheck.m
index 886c2a9dd..4d879760a 100644
--- a/compiler/post_typecheck.m
+++ b/compiler/post_typecheck.m
@@ -467,11 +467,11 @@ post_typecheck__resolve_pred_overloading(PredId0, Args0, CallerPredInfo,
% have the specified name and arity
%
pred_info_typevarset(CallerPredInfo, TVarSet),
- pred_info_import_status(CallerPredInfo, Status),
+ pred_info_get_markers(CallerPredInfo, Markers),
pred_info_clauses_info(CallerPredInfo, ClausesInfo),
clauses_info_vartypes(ClausesInfo, VarTypes),
map__apply_to_list(Args0, VarTypes, ArgTypes),
- typecheck__resolve_pred_overloading(ModuleInfo, Status,
+ typecheck__resolve_pred_overloading(ModuleInfo, Markers,
ArgTypes, TVarSet, PredName0, PredName, PredId)
;
PredId = PredId0,
@@ -638,9 +638,10 @@ resolve_aditi_builtin_overloading(ModuleInfo, CallerPredInfo, Args,
EvalMethod \= normal
->
call(AdjustArgTypes, ArgTypes0, ArgTypes),
- pred_info_import_status(CallerPredInfo, Status),
- typecheck__resolve_pred_overloading(ModuleInfo, Status,
- ArgTypes, TVarSet, SymName0, SymName, PredId)
+ pred_info_get_markers(CallerPredInfo, Markers),
+ typecheck__resolve_pred_overloading(ModuleInfo,
+ Markers, ArgTypes, TVarSet,
+ SymName0, SymName, PredId)
;
error(
"post_typecheck__resolve_aditi_builtin_overloading")
@@ -1246,10 +1247,10 @@ post_typecheck__resolve_unify_functor(X0, ConsId0, ArgVars0, Mode0,
%
\+ pred_info_is_field_access_function(ModuleInfo, PredInfo0),
- pred_info_import_status(PredInfo0, Status),
+ pred_info_get_markers(PredInfo0, Markers),
module_info_get_predicate_table(ModuleInfo, PredTable),
predicate_table_search_func_sym_arity(PredTable,
- calls_are_fully_qualified(Status),
+ calls_are_fully_qualified(Markers),
PredName, Arity, PredIds),
% Check if any of the candidate functions have
@@ -1300,8 +1301,8 @@ post_typecheck__resolve_unify_functor(X0, ConsId0, ArgVars0, Mode0,
map__apply_to_list(ArgVars0, VarTypes0, ArgTypes0),
AllArgTypes = ArgTypes0 ++ HOArgTypes,
pred_info_typevarset(PredInfo0, TVarSet),
- pred_info_import_status(PredInfo0, Status),
- get_pred_id(calls_are_fully_qualified(Status), Name,
+ pred_info_get_markers(PredInfo0, Markers),
+ get_pred_id(calls_are_fully_qualified(Markers), Name,
PredOrFunc, TVarSet, AllArgTypes, ModuleInfo, PredId)
->
get_proc_id(ModuleInfo, PredId, ProcId),
diff --git a/compiler/prog_io.m b/compiler/prog_io.m
index 06512f25c..76445dee7 100644
--- a/compiler/prog_io.m
+++ b/compiler/prog_io.m
@@ -185,7 +185,8 @@
:- pred parse_type_defn_head(module_name, term, term, maybe_functor).
:- mode parse_type_defn_head(in, in, in, out) is det.
- % get_maybe_equality_compare_preds(Body0, Body, MaybeEqualPred):
+ % get_maybe_equality_compare_preds(ModuleName,
+ % Body0, Body, MaybeEqualPred):
% Checks if `Body0' is a term of the form
% `
where equality is '
% ` where comparison is '
@@ -195,9 +196,9 @@
% MaybeEqualPred. If not, returns Body = Body0
% and `no' in MaybeEqualPred.
-:- pred get_maybe_equality_compare_preds(term, term,
+:- pred get_maybe_equality_compare_preds(module_name, term, term,
maybe1(maybe(unify_compare))).
-:- mode get_maybe_equality_compare_preds(in, out, out) is det.
+:- mode get_maybe_equality_compare_preds(in, in, out, out) is det.
%-----------------------------------------------------------------------------%
@@ -1522,7 +1523,7 @@ add_error(Error, Term, Msgs, [Msg - Term | Msgs]) :-
parse_type_decl_type(ModuleName, "--->", [H, B], Condition, R) :-
/* get_condition(...), */
Condition = true,
- get_maybe_equality_compare_preds(B, Body, EqCompare),
+ get_maybe_equality_compare_preds(ModuleName, B, Body, EqCompare),
process_du_type(ModuleName, H, Body, EqCompare, R).
parse_type_decl_type(ModuleName, "==", [H, B], Condition, R) :-
@@ -1645,7 +1646,7 @@ parse_mode_decl_pred(ModuleName, VarSet, Pred, Attributes, Result) :-
%-----------------------------------------------------------------------------%
-get_maybe_equality_compare_preds(B, Body, MaybeEqComp) :-
+get_maybe_equality_compare_preds(ModuleName, B, Body, MaybeEqComp) :-
(
B = term__functor(term__atom("where"), Args, _Context1),
Args = [Body1, EqCompTerm]
@@ -1655,14 +1656,16 @@ get_maybe_equality_compare_preds(B, Body, MaybeEqComp) :-
parse_equality_or_comparison_pred_term("equality",
EqCompTerm, PredName)
->
- parse_symbol_name(PredName, MaybeEqComp0),
+ parse_implicitly_qualified_symbol_name(ModuleName,
+ PredName, MaybeEqComp0),
process_maybe1(make_equality, MaybeEqComp0,
MaybeEqComp)
;
parse_equality_or_comparison_pred_term("comparison",
EqCompTerm, PredName)
->
- parse_symbol_name(PredName, MaybeEqComp0),
+ parse_implicitly_qualified_symbol_name(ModuleName,
+ PredName, MaybeEqComp0),
process_maybe1(make_comparison, MaybeEqComp0,
MaybeEqComp)
;
@@ -1673,9 +1676,10 @@ get_maybe_equality_compare_preds(B, Body, MaybeEqComp) :-
parse_equality_or_comparison_pred_term("comparison",
CompTerm, CompPredNameTerm)
->
- parse_symbol_name(EqPredNameTerm, EqPredNameResult),
- parse_symbol_name(CompPredNameTerm,
- CompPredNameResult),
+ parse_implicitly_qualified_symbol_name(ModuleName,
+ EqPredNameTerm, EqPredNameResult),
+ parse_implicitly_qualified_symbol_name(ModuleName,
+ CompPredNameTerm, CompPredNameResult),
(
EqPredNameResult = ok(EqPredName),
CompPredNameResult = ok(CompPredName),
diff --git a/compiler/prog_io_pragma.m b/compiler/prog_io_pragma.m
index 2e74a9a55..b62c9c2ad 100644
--- a/compiler/prog_io_pragma.m
+++ b/compiler/prog_io_pragma.m
@@ -37,7 +37,7 @@ parse_pragma(ModuleName, VarSet, PragmaTerms, Result) :-
(
% new syntax: `:- pragma foo(...).'
PragmaTerms = [SinglePragmaTerm0],
- get_maybe_equality_compare_preds(SinglePragmaTerm0,
+ get_maybe_equality_compare_preds(ModuleName, SinglePragmaTerm0,
SinglePragmaTerm, UnifyCompareResult),
SinglePragmaTerm = term__functor(term__atom(PragmaType),
PragmaArgs, _),
diff --git a/compiler/purity.m b/compiler/purity.m
index 1db8d874e..62fd9cf0f 100644
--- a/compiler/purity.m
+++ b/compiler/purity.m
@@ -754,9 +754,9 @@ check_higher_order_purity(GoalInfo, ConsId, Var, Args, ActualPurity) -->
{ list__append(ArgTypes0, VarArgTypes, PredArgTypes) },
ModuleInfo =^ module_info,
CallerPredInfo =^ pred_info,
- { pred_info_import_status(CallerPredInfo, CallerStatus) },
+ { pred_info_get_markers(CallerPredInfo, CallerMarkers) },
(
- { get_pred_id(calls_are_fully_qualified(CallerStatus),
+ { get_pred_id(calls_are_fully_qualified(CallerMarkers),
PName, PredOrFunc, TVarSet, PredArgTypes,
ModuleInfo, CalleePredId) }
->
diff --git a/compiler/typecheck.m b/compiler/typecheck.m
index f11734f88..013652573 100644
--- a/compiler/typecheck.m
+++ b/compiler/typecheck.m
@@ -127,7 +127,7 @@
% Abort if there is no matching pred.
% Abort if there are multiple matching preds.
-:- pred typecheck__resolve_pred_overloading(module_info, import_status,
+:- pred typecheck__resolve_pred_overloading(module_info, pred_markers,
list(type), tvarset, sym_name, sym_name, pred_id).
:- mode typecheck__resolve_pred_overloading(in,
in, in, in, in, out, out) is det.
@@ -471,10 +471,11 @@ typecheck_pred_type(Iteration, PredId, !PredInfo,
;
IsFieldAccessFunction = no
),
+ pred_info_get_markers(!.PredInfo, Markers),
typecheck_info_init(!.IOState, ModuleInfo, PredId,
IsFieldAccessFunction, TypeVarSet0, VarSet,
ExplicitVarTypes0, HeadTypeParams1,
- Constraints, Status, TypeCheckInfo1),
+ Constraints, Status, Markers, TypeCheckInfo1),
typecheck_info_get_type_assign_set(TypeCheckInfo1,
OrigTypeAssignSet),
typecheck_clause_list(Clauses1, HeadVars, ArgTypes0, Clauses,
@@ -927,7 +928,10 @@ maybe_add_field_access_function_clause(ModuleInfo, PredInfo0, PredInfo) :-
Clause = clause(ProcIds, Goal, mercury, Context),
clauses_info_set_clauses(ClausesInfo0, [Clause], ClausesInfo),
pred_info_update_goal_type(PredInfo0, clauses, PredInfo1),
- pred_info_set_clauses_info(PredInfo1, ClausesInfo, PredInfo)
+ pred_info_set_clauses_info(PredInfo1, ClausesInfo, PredInfo2),
+ pred_info_get_markers(PredInfo2, Markers0),
+ add_marker(Markers0, calls_are_fully_qualified, Markers),
+ pred_info_set_markers(PredInfo2, Markers, PredInfo)
;
PredInfo = PredInfo0
).
@@ -1703,7 +1707,7 @@ typecheck_call_pred_adjust_arg_types(CallId, Args, AdjustArgTypes,
CallId = PorF - SymName/Arity,
predicate_table_search_pf_sym_arity(PredicateTable,
calls_are_fully_qualified(
- TypeCheckInfo1 ^ import_status),
+ TypeCheckInfo1 ^ pred_markers),
PorF, SymName, Arity, PredIdList)
->
% handle the case of a non-overloaded predicate specially
@@ -1806,7 +1810,7 @@ report_pred_call_error(PredCallId, TypeCheckInfo1, TypeCheckInfo) :-
(
predicate_table_search_pf_sym(PredicateTable,
calls_are_fully_qualified(
- TypeCheckInfo1 ^ import_status),
+ TypeCheckInfo1 ^ pred_markers),
PredOrFunc0, SymName, OtherIds),
predicate_table_get_preds(PredicateTable, Preds),
OtherIds \= []
@@ -1820,7 +1824,7 @@ report_pred_call_error(PredCallId, TypeCheckInfo1, TypeCheckInfo) :-
),
predicate_table_search_pf_sym(PredicateTable,
calls_are_fully_qualified(
- TypeCheckInfo1 ^ import_status),
+ TypeCheckInfo1 ^ pred_markers),
PredOrFunc, SymName, OtherIds),
OtherIds \= []
->
@@ -1895,12 +1899,13 @@ get_overloaded_pred_arg_types([PredId | PredIds], Preds, AdjustArgTypes,
% module qualified, so they should not be considered
% when resolving overloading.
-typecheck__resolve_pred_overloading(ModuleInfo, Status, ArgTypes, TVarSet,
- PredName0, PredName, PredId) :-
+typecheck__resolve_pred_overloading(ModuleInfo, CallerMarkers,
+ ArgTypes, TVarSet, PredName0, PredName, PredId) :-
module_info_get_predicate_table(ModuleInfo, PredTable),
(
predicate_table_search_pred_sym(PredTable,
- calls_are_fully_qualified(Status), PredName0, PredIds0)
+ calls_are_fully_qualified(CallerMarkers),
+ PredName0, PredIds0)
->
PredIds = PredIds0
;
@@ -3058,7 +3063,7 @@ builtin_pred_type(TypeCheckInfo, Functor, Arity, PredConsInfoList) :-
(
predicate_table_search_sym(PredicateTable,
calls_are_fully_qualified(
- TypeCheckInfo ^ import_status),
+ TypeCheckInfo ^ pred_markers),
SymName, PredIdList)
->
predicate_table_get_preds(PredicateTable, Preds),
@@ -3536,6 +3541,9 @@ project_rename_flip_class_constraints(CallTVars, TVarRenaming,
% Import status of the pred
% being checked
+ pred_markers :: pred_markers,
+ % Markers of the pred being checked
+
is_field_access_function :: bool,
% Is the pred we're checking
% a field access function?
@@ -3577,7 +3585,7 @@ project_rename_flip_class_constraints(CallTVars, TVarRenaming,
/*
:- inst uniq_typecheck_info = bound_unique(
typecheck_info(
- unique, ground,
+ unique, ground, ground,
ground, ground, ground, ground,
ground, ground, ground, ground,
ground, ground, ground
@@ -3596,7 +3604,7 @@ project_rename_flip_class_constraints(CallTVars, TVarRenaming,
/*
:- inst typecheck_info_no_io = bound_unique(
typecheck_info(
- dead, ground,
+ dead, ground, ground,
ground, ground, ground, ground,
ground, ground, ground, ground,
ground, ground, ground
@@ -3615,14 +3623,14 @@ project_rename_flip_class_constraints(CallTVars, TVarRenaming,
:- pred typecheck_info_init(io__state, module_info, pred_id, bool, tvarset,
prog_varset, map(prog_var, type), headtypes, class_constraints,
- import_status, typecheck_info).
-:- mode typecheck_info_init(di, in, in, in, in, in, in, in, in, in,
+ import_status, pred_markers, typecheck_info).
+:- mode typecheck_info_init(di, in, in, in, in, in, in, in, in, in, in,
typecheck_info_uo)
is det.
typecheck_info_init(IOState0, ModuleInfo, PredId, IsFieldAccessFunction,
TypeVarSet, VarSet, VarTypes, HeadTypeParams,
- Constraints, Status, TypeCheckInfo) :-
+ Constraints, Status, Markers, TypeCheckInfo) :-
CallPredId = call(predicate - unqualified("") / 0),
term__context_init(Context),
map__init(TypeBindings),
@@ -3631,7 +3639,7 @@ typecheck_info_init(IOState0, ModuleInfo, PredId, IsFieldAccessFunction,
WarnedAboutOverloading = no,
unsafe_promise_unique(IOState0, IOState), % XXX
TypeCheckInfo = typecheck_info(
- IOState, ModuleInfo, CallPredId, 0, PredId, Status,
+ IOState, ModuleInfo, CallPredId, 0, PredId, Status, Markers,
IsFieldAccessFunction, Context,
unify_context(explicit, []), VarSet,
[type_assign(VarTypes, TypeVarSet, HeadTypeParams,
diff --git a/doc/reference_manual.texi b/doc/reference_manual.texi
index 14ad876ac..d880352ff 100644
--- a/doc/reference_manual.texi
+++ b/doc/reference_manual.texi
@@ -3465,6 +3465,12 @@ could be any of @samp{det}, @samp{failure} or @samp{erroneous}).
@item
The equality predicate must be ``pure'' (@pxref{Impurity}).
+@item
+The equality predicate must be defined in the same module as the type.
+
+@item
+If the type is exported the equality predicate must also be exported.
+
@item
@var{equalitypred} should be an equivalence relation; that is, it must be
symmetric, reflexive, and transitive. However, the compiler is not required
@@ -3535,6 +3541,12 @@ determinism to be more permissive.
@item
The comparison predicate must also be ``pure'' (@pxref{Impurity}).
+@item
+The comparison predicate must be defined in the same module as the type.
+
+@item
+If the type is exported the comparison predicate must also be exported.
+
@item
The relation
@example
diff --git a/tests/invalid/Mmakefile b/tests/invalid/Mmakefile
index a71422350..7cc1db660 100644
--- a/tests/invalid/Mmakefile
+++ b/tests/invalid/Mmakefile
@@ -16,6 +16,8 @@ MULTIMODULE_PROGS= \
aditi_update_errors \
aditi_update_mode_errors \
duplicate_instance_2 \
+ exported_unify \
+ exported_unify3 \
ho_default_func_2.sub \
import_in_parent \
imported_mode \
diff --git a/tests/invalid/exported_unify.err_exp b/tests/invalid/exported_unify.err_exp
new file mode 100644
index 000000000..fa93cf37c
--- /dev/null
+++ b/tests/invalid/exported_unify.err_exp
@@ -0,0 +1,3 @@
+exported_unify2.int:003: In clause for unification predicate for type (exported_unify2.foo):
+exported_unify2.int:003: error: undefined predicate `exported_unify2.unify_foo/2'.
+For more information, try recompiling with `-E'.
diff --git a/tests/invalid/exported_unify.m b/tests/invalid/exported_unify.m
new file mode 100644
index 000000000..238c86724
--- /dev/null
+++ b/tests/invalid/exported_unify.m
@@ -0,0 +1,13 @@
+:- module exported_unify.
+
+:- interface.
+
+:- pred unify_foo(T::in, T::in) is semidet.
+
+:- implementation.
+
+:- import_module exported_unify2.
+
+unify_foo(A, A).
+
+
diff --git a/tests/invalid/exported_unify2.m b/tests/invalid/exported_unify2.m
new file mode 100644
index 000000000..f3ababf01
--- /dev/null
+++ b/tests/invalid/exported_unify2.m
@@ -0,0 +1,14 @@
+:- module exported_unify2.
+
+:- interface.
+
+:- type foo ---> foo where equality is unify_foo.
+
+:- implementation.
+
+:- import_module std_util.
+
+:- pred unify_foo(foo::in, foo::in) is semidet.
+
+unify_foo(_, _) :- semidet_fail.
+
diff --git a/tests/invalid/exported_unify3.err_exp b/tests/invalid/exported_unify3.err_exp
new file mode 100644
index 000000000..4829d6738
--- /dev/null
+++ b/tests/invalid/exported_unify3.err_exp
@@ -0,0 +1,7 @@
+exported_unify3.m:005: In clause for unification predicate for type (exported_unify3.foo):
+exported_unify3.m:005: error: undefined predicate `exported_unify3.defined_in_wrong_module/2'.
+exported_unify3.sub.int:003: In clause for unification predicate for type ((exported_unify3.sub).bar):
+exported_unify3.sub.int:003: error: undefined predicate `exported_unify3.sub.not_exported/2'.
+exported_unify3.int0:003: In clause for unification predicate for type (exported_unify3.foo):
+exported_unify3.int0:003: error: undefined predicate `exported_unify3.defined_in_wrong_module/2'.
+For more information, try recompiling with `-E'.
diff --git a/tests/invalid/exported_unify3.m b/tests/invalid/exported_unify3.m
new file mode 100644
index 000000000..2fb70915c
--- /dev/null
+++ b/tests/invalid/exported_unify3.m
@@ -0,0 +1,33 @@
+:- module exported_unify3.
+
+:- interface.
+
+:- type foo ---> foo where equality is defined_in_wrong_module.
+
+ :- module exported_unify3.sub.
+
+ :- interface.
+
+ :- type bar ---> bar where equality is not_exported.
+
+ :- pred defined_in_wrong_module(foo::in, foo::in) is semidet.
+
+ :- end_module exported_unify3.sub.
+
+:- implementation.
+
+:- import_module exported_unify3.sub.
+
+ :- module exported_unify3.sub.
+
+ :- implementation.
+
+ :- import_module std_util.
+
+ defined_in_wrong_module(_, _) :- semidet_fail.
+
+ :- pred not_exported(bar::in, bar::in) is semidet.
+
+ not_exported(_, _) :- semidet_fail.
+
+ :- end_module exported_unify3.sub.