Files
mercury/compiler/typecheck.m
Zoltan Somogyi 291879c8bb Move checks for type_infos to add_foreign_proc.m.
This allows to perform those checks for *all* foreign_procs,
not just the ones that get added to the HLDS.

compiler/add_foreign_proc.m:
    Move the code that checks the bodies of foreign_procs for the
    presence of type_info variables for existentially quantified
    type variables here from typecheck.m and typecheck_errors.m.
    Change the diagnostic's wording to match our new phraseology.

    Record identifiers in a set, not a list, for faster membership tests,
    since we now do even more of them.

compiler/foreign.m:
    Provide a mechanism to return the identifiers not just in the
    non-comment parts of foreign_procs, but the comment parts as well,
    since add_foreign_proc.m now needs this functionality.

compiler/make_hlds_warn.m:
    Conform to the change in foreign.m.

compiler/typecheck.m:
compiler/typecheck_errors.m:
    Delete the code that was moved (in a modified form)
    to add_foreign_proc.m.

compiler/ml_foreign_proc_gen.m:
    Update a reference in a comment.

tests/invalid/exist_foreign_error.err_exp:
    Expect the updated wording of the affected diagnostics,
    and expect diagnostics for *all* the foreign_procs in the test,
    regardless of which language they are for.

tests/invalid/exist_foreign_error.err_exp2:
tests/invalid/exist_foreign_error.err_exp3:
    Delete these files, since the output they expect is now
    in the .err_exp file.

tests/invalid/fp_dup_bug.err_exp2:
tests/invalid/fp_dup_bug.err_exp3:
tests/invalid/gh72_errors.err_exp2:
tests/invalid/gh72_errors.err_exp3:
    Expect the updated wording of diagnostics affected by previous
    changes (which updated the .err_exp files for C, not these for
    Java and C#).
2026-01-31 16:31:04 +11:00

1034 lines
45 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 1993-2012 The University of Melbourne.
% Copyright (C) 2014-2026 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.
%---------------------------------------------------------------------------%
%
% File: typecheck.m.
% Main author: fjh.
%
% This file contains the Mercury type-checker.
%
% The predicates in this module are named as follows:
%
% - Predicates that type check a particular language construct
% (goal, clause, etc.) are called typecheck_*. These will eventually
% have to iterate over every type assignment in the type assignment set.
%
% - Predicates that unify two things with respect to a single type assignment,
% as opposed to a type assignment set are called type_assign_*.
%
% There are four sorts of types:
%
% 1 Discriminated union types, such as
% :- type tree(T) ---> nil ; t(tree(T), T, tree(T)).
%
% 2 Equivalence types, such as
% :- type real == float.
% Any number of types can be equivalent; the *canonical* one is the one
% which is not defined using "==".
%
% Currently references to equivalence types are expanded in a separate pass
% by mercury_compile_front_end.m. It would be better to avoid expanding them
% (and instead modify the type unification algorithm to handle equivalent
% types) because this would give better error messages. However, this is
% not a high priority.
%
% 3 Higher-order predicate and function types
% pred, pred(T), pred(T1, T2), pred(T1, T2, T3), ...
% func(T1) = T2, func(T1, T2) = T3, ...
%
% 4 Builtin types, such as
% character, int, float, string.
% These types have special syntax for constants. There may be other types
% (list(T), unit, univ, etc.) provided by the system, but they can just be
% part of the standard library.
%
% Each exported predicate must have a `:- pred' declaration specifying the
% types of the arguments for that predicate. For predicates that are
% local to a module, we can infer the types.
%
%---------------------------------------------------------------------------%
%
% Known Bugs:
%
% XXX Type inference doesn't handle ambiguity as well as it could do.
% We should do a topological sort, and then typecheck it all bottom-up.
% If we infer an ambiguous type for a pred, we should not reject it
% immediately; instead we should give it an overloaded type, and keep going.
% When we have finished type inference, we should then delete unused
% overloadings, and only then should we report ambiguity errors,
% if any overloading still remains.
%
% Wish list:
%
% - We should handle equivalence types here.
%
%---------------------------------------------------------------------------%
:- module check_hlds.typecheck.
:- interface.
:- import_module hlds.
:- import_module hlds.hlds_clauses.
:- import_module hlds.hlds_module.
:- import_module parse_tree.
:- import_module parse_tree.error_spec.
:- import_module io.
:- import_module list.
%---------------------------------------------------------------------------%
:- type number_of_iterations
---> within_iteration_limit
; exceeded_iteration_limit.
% typecheck_module(ProgressStream, !ModuleInfo, Specs, FoundSyntaxError,
% NumberOfIterations):
%
% Type checks ModuleInfo and annotates it with variable type information.
% Specs is set to the list of errors and warnings found, plus messages
% about the predicates and functions whose types have been inferred.
% We set FoundSyntaxError to `some_clause_syntax_errors' if some of
% the clauses in the typechecked predicates contained syntax errors.
% We set NumberOfIterations to `exceeded_iteration_limit'
% iff the type inference iteration limit was reached.
%
:- pred typecheck_module(io.text_output_stream::in,
module_info::in, module_info::out, list(error_spec)::out,
maybe_clause_syntax_errors::out, number_of_iterations::out) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module check_hlds.type_assign.
:- import_module check_hlds.typecheck_clauses.
:- import_module check_hlds.typecheck_error_overload.
:- import_module check_hlds.typecheck_error_undef.
:- import_module check_hlds.typecheck_errors.
:- import_module check_hlds.typecheck_info.
:- import_module check_hlds.typecheck_msgs.
:- import_module check_hlds.typeclasses.
:- import_module hlds.goal_util.
:- import_module hlds.hlds_args.
:- import_module hlds.hlds_class.
:- import_module hlds.hlds_data.
:- import_module hlds.hlds_error_util.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_markers.
:- import_module hlds.hlds_pred.
:- import_module hlds.hlds_rtti.
:- import_module hlds.instmap.
:- import_module hlds.make_goal.
:- import_module hlds.passes_aux.
:- import_module hlds.pred_name.
:- import_module hlds.pred_table.
:- import_module hlds.special_pred.
:- import_module hlds.status.
:- import_module hlds.var_table_hlds.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.options.
:- import_module mdbcomp.
:- import_module mdbcomp.builtin_modules.
:- import_module mdbcomp.prim_data.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.builtin_lib_types.
:- import_module parse_tree.error_util.
:- import_module parse_tree.file_names. % undesirable dependency
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_type.
:- import_module parse_tree.prog_type_scan.
:- import_module parse_tree.prog_type_subst.
:- import_module parse_tree.prog_type_unify.
:- import_module parse_tree.var_table.
:- import_module parse_tree.vartypes.
:- import_module parse_tree.write_error_spec.
:- import_module assoc_list.
:- import_module bool.
:- import_module int.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module set.
:- import_module set_tree234.
:- import_module term_context.
:- import_module term_subst.
:- import_module varset.
%---------------------------------------------------------------------------%
typecheck_module(ProgressStream, !ModuleInfo, Specs, FoundSyntaxError,
NumberOfIterations) :-
module_info_get_globals(!.ModuleInfo, Globals),
globals.lookup_int_option(Globals, type_inference_iteration_limit,
MaxIterations),
module_info_get_valid_pred_id_set(!.ModuleInfo, OrigValidPredIdSet),
OrigValidPredIds = set_tree234.to_sorted_list(OrigValidPredIdSet),
typecheck_to_fixpoint(ProgressStream, 1, MaxIterations, !ModuleInfo,
OrigValidPredIds, OrigValidPredIdSet, FinalValidPredIdSet,
CheckSpecs, FoundSyntaxError, NumberOfIterations),
construct_type_inference_messages(!.ModuleInfo, FinalValidPredIdSet,
OrigValidPredIds, [], InferSpecs),
Specs = InferSpecs ++ CheckSpecs.
% Repeatedly typecheck the code for a group of predicates
% until a fixpoint is reached, or until some errors are detected.
%
:- pred typecheck_to_fixpoint(io.text_output_stream::in, int::in, int::in,
module_info::in, module_info::out,
list(pred_id)::in, set_tree234(pred_id)::in, set_tree234(pred_id)::out,
list(error_spec)::out, maybe_clause_syntax_errors::out,
number_of_iterations::out) is det.
typecheck_to_fixpoint(ProgressStream, Iteration, MaxIterations, !ModuleInfo,
OrigValidPredIds, OrigValidPredIdSet, FinalValidPredIdSet,
Specs, FoundSyntaxError, NumberOfIterations) :-
module_info_get_pred_id_table(!.ModuleInfo, PredIdTable0),
map.to_assoc_list(PredIdTable0, PredIdsInfos0),
typecheck_module_one_iteration(ProgressStream, !.ModuleInfo,
OrigValidPredIdSet, PredIdsInfos0, [], RevPredIdsInfos,
[], NewlyInvalidPredIds, [], CurSpecs,
no_clause_syntax_errors, CurFoundSyntaxError,
next_iteration_is_not_needed, NextIteration),
map.from_rev_sorted_assoc_list(RevPredIdsInfos, PredIdTable),
module_info_set_pred_id_table(PredIdTable, !ModuleInfo),
module_info_make_pred_ids_invalid(NewlyInvalidPredIds, !ModuleInfo),
module_info_get_valid_pred_id_set(!.ModuleInfo, NewValidPredIdSet),
module_info_get_globals(!.ModuleInfo, Globals),
( if
( NextIteration = next_iteration_is_not_needed
; contains_errors(Globals, CurSpecs) = yes
)
then
FinalValidPredIdSet = NewValidPredIdSet,
Specs = CurSpecs,
FoundSyntaxError = CurFoundSyntaxError,
NumberOfIterations = within_iteration_limit
else
globals.lookup_bool_option(Globals, debug_types, DebugTypes),
(
DebugTypes = yes,
construct_type_inference_messages(!.ModuleInfo, NewValidPredIdSet,
OrigValidPredIds, [], ProgressSpecs),
trace [io(!IO)] (
module_info_get_name(!.ModuleInfo, ModuleName),
get_debug_output_stream(Globals, ModuleName, OutputStream,
!IO),
write_error_specs(OutputStream, Globals, ProgressSpecs, !IO)
)
;
DebugTypes = no
),
( if Iteration < MaxIterations then
typecheck_to_fixpoint(ProgressStream, Iteration + 1, MaxIterations,
!ModuleInfo, OrigValidPredIds,
OrigValidPredIdSet, FinalValidPredIdSet, Specs,
FoundSyntaxError, NumberOfIterations)
else
FinalValidPredIdSet = NewValidPredIdSet,
Specs = [typecheck_report_max_iterations_exceeded(MaxIterations)],
FoundSyntaxError = CurFoundSyntaxError,
NumberOfIterations = exceeded_iteration_limit
)
).
%---------------------------------------------------------------------------%
:- type next_iteration
---> next_iteration_is_not_needed
; next_iteration_is_needed.
% Iterate over the list of pred_ids in a module.
%
:- pred typecheck_module_one_iteration(io.text_output_stream::in,
module_info::in, set_tree234(pred_id)::in,
assoc_list(pred_id, pred_info)::in,
assoc_list(pred_id, pred_info)::in, assoc_list(pred_id, pred_info)::out,
list(pred_id)::in, list(pred_id)::out,
list(error_spec)::in, list(error_spec)::out,
maybe_clause_syntax_errors::in, maybe_clause_syntax_errors::out,
next_iteration::in, next_iteration::out) is det.
typecheck_module_one_iteration(_, _, _, [], !RevPredIdsInfos,
!NewlyInvalidPredIds, !Specs, !FoundSyntaxError, !NextIteration).
typecheck_module_one_iteration(ProgressStream, ModuleInfo, ValidPredIdSet,
[HeadPredIdInfo0 | TailPredIdsInfos0], !RevPredIdsInfos,
!NewlyInvalidPredIds, !Specs, !FoundSyntaxError, !NextIteration) :-
HeadPredIdInfo0 = PredId - PredInfo0,
( if
(
pred_info_is_imported(PredInfo0)
;
not set_tree234.contains(ValidPredIdSet, PredId)
)
then
!:RevPredIdsInfos = [HeadPredIdInfo0 | !.RevPredIdsInfos],
typecheck_module_one_iteration(ProgressStream, ModuleInfo,
ValidPredIdSet, TailPredIdsInfos0, !RevPredIdsInfos,
!NewlyInvalidPredIds, !Specs, !FoundSyntaxError, !NextIteration)
else
% Potential parallelization site.
typecheck_pred_if_needed(ProgressStream, ModuleInfo, PredId,
PredInfo0, PredInfo, PredSpecs, PredSyntaxError, ContainsErrors,
PredNextIteration),
(
ContainsErrors = no
;
ContainsErrors = yes,
% This code is not needed at the moment, since currently we don't
% run mode analysis if there are any type errors. And this code
% also causes problems: if there are undefined modes, it can end up
% calling error/1, since post_finish_ill_typed_pred assumes that
% there are no undefined modes.
%
% If we get an error, we need to call post_finish_ill_typed_pred
% on the pred, to ensure that its mode declaration gets properly
% module qualified; then we call `remove_predid', so that the
% predicate's definition will be ignored by later passes
% (the declaration will still be used to check any calls to it).
%
% post_finish_ill_typed_pred(ModuleInfo0, PredId,
% PredInfo1, PredInfo)
!:NewlyInvalidPredIds = [PredId | !.NewlyInvalidPredIds]
),
HeadPredIdInfo = PredId - PredInfo,
!:Specs = PredSpecs ++ !.Specs,
(
PredSyntaxError = some_clause_syntax_errors,
!:FoundSyntaxError = some_clause_syntax_errors
;
PredSyntaxError = no_clause_syntax_errors
),
(
PredNextIteration = next_iteration_is_not_needed
;
PredNextIteration = next_iteration_is_needed,
!:NextIteration = next_iteration_is_needed
),
!:RevPredIdsInfos = [HeadPredIdInfo | !.RevPredIdsInfos],
typecheck_module_one_iteration(ProgressStream, ModuleInfo,
ValidPredIdSet, TailPredIdsInfos0, !RevPredIdsInfos,
!NewlyInvalidPredIds, !Specs, !FoundSyntaxError, !NextIteration)
).
:- pred typecheck_pred_if_needed(io.text_output_stream::in, module_info::in,
pred_id::in, pred_info::in, pred_info::out, list(error_spec)::out,
maybe_clause_syntax_errors::out, bool::out, next_iteration::out) is det.
typecheck_pred_if_needed(ProgressStream, ModuleInfo, PredId, !PredInfo,
!:Specs, FoundSyntaxError, ContainsErrors, NextIteration) :-
( if is_pred_created_type_correct(ModuleInfo, !PredInfo) then
!:Specs = [],
FoundSyntaxError = no_clause_syntax_errors,
ContainsErrors = no,
NextIteration = next_iteration_is_not_needed
else
pred_info_get_clauses_info(!.PredInfo, ClausesInfo0),
clauses_info_get_had_syntax_errors(ClausesInfo0, FoundSyntaxError),
handle_stubs(ModuleInfo, PredId, !PredInfo, FoundSyntaxError,
!:Specs, MaybeNeedTypecheck),
(
MaybeNeedTypecheck = do_not_need_typecheck(ContainsErrors,
NextIteration)
;
MaybeNeedTypecheck = do_need_typecheck,
do_typecheck_pred(ProgressStream, ModuleInfo, PredId, !PredInfo,
!Specs, NextIteration),
module_info_get_globals(ModuleInfo, Globals),
ContainsErrors = contains_errors(Globals, !.Specs)
)
).
:- pred is_pred_created_type_correct(module_info::in,
pred_info::in, pred_info::out) is semidet.
is_pred_created_type_correct(ModuleInfo, !PredInfo) :-
( if
(
% Most compiler-generated unify and compare predicates are created
% already type-correct, so there is no need to typecheck them.
% The exceptions are unify and compare predicates that either
%
% - call a user-defined predicate, or
% - involve existentially quantified type variables.
is_unify_index_or_compare_pred(!.PredInfo),
not special_pred_needs_typecheck(ModuleInfo, !.PredInfo)
;
% Most predicates for builtins are also created already
% type-correct. The exceptions still need to have their stub
% clauses generated; these are marked with marker_builtin_stub.
% XXX Why the delay?
pred_info_is_builtin(!.PredInfo),
pred_info_get_markers(!.PredInfo, Markers),
not marker_is_present(Markers, marker_builtin_stub)
)
then
pred_info_get_clauses_info(!.PredInfo, ClausesInfo0),
clauses_info_get_clauses_rep(ClausesInfo0, ClausesRep0, _ItemNumbers),
IsEmpty = clause_list_is_empty(ClausesRep0),
(
IsEmpty = yes,
pred_info_mark_as_external(!PredInfo)
;
IsEmpty = no
)
else
fail
).
:- type maybe_need_typecheck
---> do_not_need_typecheck(
notc_contains_errors :: bool,
notc_next_iteration :: next_iteration
)
; do_need_typecheck.
% This predicate handles stubs, and implements the --allow-stubs
% and --warn-stubs options.
%
% If --allow-stubs is set, and there are no clauses, then
% - issue a warning (if --warn-stubs is set), and then
% - generate a "stub" clause that just throws an exception.
%
:- pred handle_stubs(module_info::in, pred_id::in,
pred_info::in, pred_info::out, maybe_clause_syntax_errors::in,
list(error_spec)::out, maybe_need_typecheck::out) is det.
handle_stubs(ModuleInfo, PredId, !PredInfo, FoundSyntaxError,
!:Specs, MaybeNeedTypecheck) :-
pred_info_get_markers(!.PredInfo, Markers0),
pred_info_get_clauses_info(!.PredInfo, ClausesInfo0),
clauses_info_get_clauses_rep(ClausesInfo0, ClausesRep0, _ItemNumbers0),
clause_list_is_empty(ClausesRep0) = ClausesRep0IsEmpty,
(
ClausesRep0IsEmpty = yes,
module_info_get_globals(ModuleInfo, Globals),
% There are no clauses, so there can be no clause non-contiguity
% errors.
( if
globals.lookup_bool_option(Globals, allow_stubs, yes),
not marker_is_present(Markers0, marker_class_method)
then
!:Specs =
maybe_report_no_clauses_stub(ModuleInfo, PredId, !.PredInfo),
generate_and_add_stub_clause(ModuleInfo, PredId, !PredInfo)
else if
marker_is_present(Markers0, marker_builtin_stub)
then
!:Specs = [],
generate_and_add_stub_clause(ModuleInfo, PredId, !PredInfo)
else
!:Specs = []
)
;
ClausesRep0IsEmpty = no,
!:Specs = []
),
% The above code may add stub clauses to the predicate, which would
% invalidate ClausesInfo0.
pred_info_get_clauses_info(!.PredInfo, ClausesInfo1),
clauses_info_get_clauses_rep(ClausesInfo1, ClausesRep1, _ItemNumbers),
clause_list_is_empty(ClausesRep1) = ClausesRep1IsEmpty,
(
ClausesRep1IsEmpty = yes,
expect(unify(!.Specs, []), $pred, "starting Specs not empty"),
% There are no clauses for class methods. The clauses are generated
% later on, in polymorphism.expand_class_method_bodies.
% XXX Why the delay?
( if marker_is_present(Markers0, marker_class_method) then
% For the moment, we just insert the types of the head vars
% into the clauses_info.
clauses_info_get_headvar_list(ClausesInfo1, HeadVars),
pred_info_get_arg_types(!.PredInfo, _ArgTypeVarSet, _ExistQVars,
ArgTypes),
vartypes_from_corresponding_lists(HeadVars, ArgTypes, VarTypes),
clauses_info_get_varset(ClausesInfo1, VarSet),
corresponding_vars_types_to_var_table(ModuleInfo, VarSet,
HeadVars, ArgTypes, VarTable),
clauses_info_set_explicit_vartypes(VarTypes,
ClausesInfo1, ClausesInfo2),
clauses_info_set_var_table(VarTable, ClausesInfo2, ClausesInfo),
pred_info_set_clauses_info(ClausesInfo, !PredInfo),
% We also need to set the external_type_params field
% to indicate that all the existentially quantified tvars
% in the head of this pred are indeed bound by this predicate.
type_vars_in_types(ArgTypes, HeadVarsInclExistentials),
pred_info_set_external_type_params(HeadVarsInclExistentials,
!PredInfo),
ContainsErrors = no,
!:Specs = []
else
ContainsErrors = yes,
(
FoundSyntaxError = no_clause_syntax_errors,
!:Specs =
maybe_report_no_clauses(ModuleInfo, PredId, !.PredInfo)
;
FoundSyntaxError = some_clause_syntax_errors,
% There were clauses, they just had errors. Printing a message
% saying that there were no clauses would be misleading,
% and the messages for the syntax errors will mean that
% this compiler invocation won't succeed anyway.
!:Specs = []
)
),
MaybeNeedTypecheck = do_not_need_typecheck(ContainsErrors,
next_iteration_is_not_needed)
;
ClausesRep1IsEmpty = no,
(
FoundSyntaxError = no_clause_syntax_errors,
MaybeNeedTypecheck = do_need_typecheck
;
FoundSyntaxError = some_clause_syntax_errors,
% Printing the messages we generated above could be misleading,
% and the messages for the syntax errors will mean that
% this compiler invocation won't succeed anyway.
!:Specs = [],
ContainsErrors = yes,
MaybeNeedTypecheck = do_not_need_typecheck(ContainsErrors,
next_iteration_is_not_needed)
)
).
%---------------------------------------------------------------------------%
:- pred do_typecheck_pred(io.text_output_stream::in, module_info::in,
pred_id::in, pred_info::in, pred_info::out,
list(error_spec)::in, list(error_spec)::out, next_iteration::out) is det.
do_typecheck_pred(ProgressStream, ModuleInfo, PredId, !PredInfo,
!Specs, NextIteration) :-
some [!Info, !TypeAssignSet, !ClausesInfo, !ExternalTypeParams] (
pred_info_get_clauses_info(!.PredInfo, !:ClausesInfo),
clauses_info_get_clauses_rep(!.ClausesInfo, ClausesRep0, ItemNumbers),
clauses_info_get_headvar_list(!.ClausesInfo, HeadVars),
clauses_info_get_varset(!.ClausesInfo, ClauseVarSet),
clauses_info_get_explicit_vartypes(!.ClausesInfo, ExplicitVarTypes0),
pred_info_get_status(!.PredInfo, PredStatus),
pred_info_get_typevarset(!.PredInfo, TypeVarSet0),
pred_info_get_arg_types(!.PredInfo, _ArgTypeVarSet, ExistQVars0,
ArgTypes0),
pred_info_get_markers(!.PredInfo, Markers0),
( if marker_is_present(Markers0, marker_infer_type) then
% For a predicate whose type is inferred, the predicate is allowed
% to bind the type variables in the head of the predicate's type
% declaration. Such predicates are given an initial type
% declaration of `pred foo(T1, T2, ..., TN)' by make_hlds.m.
Inferring = yes,
trace [io(!IO)] (
maybe_write_pred_progress_message(ProgressStream, ModuleInfo,
"Inferring type of", PredId, !IO)
),
!:ExternalTypeParams = [],
PredConstraints = univ_exist_constraints([], [])
else
Inferring = no,
trace [io(!IO)] (
maybe_write_pred_progress_message(ProgressStream, ModuleInfo,
"Type-checking", PredId, !IO)
),
type_vars_in_types(ArgTypes0, !:ExternalTypeParams),
pred_info_get_class_context(!.PredInfo, PredConstraints),
constraint_list_get_tvars(PredConstraints ^ univ_constraints,
UnivTVars),
!:ExternalTypeParams = UnivTVars ++ !.ExternalTypeParams,
list.sort_and_remove_dups(!ExternalTypeParams),
list.delete_elems(!.ExternalTypeParams, ExistQVars0,
!:ExternalTypeParams)
),
module_info_get_class_table(ModuleInfo, ClassTable),
make_head_hlds_constraint_db(ClassTable, TypeVarSet0,
PredConstraints, ConstraintDb),
type_assign_set_init(TypeVarSet0, ExplicitVarTypes0,
!.ExternalTypeParams, ConstraintDb, !:TypeAssignSet),
pred_info_get_markers(!.PredInfo, PredMarkers0),
typecheck_info_init(ProgressStream, ModuleInfo, PredId, !.PredInfo,
ClauseVarSet, PredStatus, PredMarkers0, !.Specs, !:Info),
get_clause_list_for_replacement(ClausesRep0, Clauses0),
typecheck_clauses(HeadVars, ArgTypes0, Clauses0, Clauses,
!TypeAssignSet, !Info),
typecheck_info_get_rhs_lambda(!.Info, MaybeRHSLambda),
(
MaybeRHSLambda = has_no_rhs_lambda
;
MaybeRHSLambda = has_rhs_lambda,
add_marker(marker_has_rhs_lambda, PredMarkers0, PredMarkers),
pred_info_set_markers(PredMarkers, !PredInfo)
),
% We need to perform a final pass of context reduction at the end,
% before checking the typeclass constraints.
pred_info_get_context(!.PredInfo, Context),
perform_context_reduction(Context, !TypeAssignSet, !Info),
typecheck_check_for_ambiguity(Context, whole_pred, HeadVars,
!.TypeAssignSet, !Info),
typecheck_check_remaining_coercion_constraints(!.TypeAssignSet, !Info),
type_assign_set_get_final_info(!.TypeAssignSet,
!.ExternalTypeParams, ExistQVars0, ExplicitVarTypes0, TypeVarSet,
!:ExternalTypeParams, InferredVarTypes, InferredTypeConstraints0,
ConstraintProofMap, ConstraintMap,
TVarRenaming, ExistTypeRenaming),
vartypes_to_sorted_assoc_list(InferredVarTypes, VarsTypes),
vars_types_to_var_table(ModuleInfo, ClauseVarSet, VarsTypes,
VarTable0),
var_table_optimize(VarTable0, VarTable),
clauses_info_set_var_table(VarTable, !ClausesInfo),
% Apply substitutions to the explicit vartypes.
(
ExistQVars0 = [],
ExplicitVarTypes1 = ExplicitVarTypes0
;
ExistQVars0 = [_ | _],
rename_vars_in_vartypes(ExistTypeRenaming,
ExplicitVarTypes0, ExplicitVarTypes1)
),
rename_vars_in_vartypes(TVarRenaming,
ExplicitVarTypes1, ExplicitVarTypes),
clauses_info_set_explicit_vartypes(ExplicitVarTypes, !ClausesInfo),
set_clause_list(Clauses, ClausesRep),
clauses_info_set_clauses_rep(ClausesRep, ItemNumbers, !ClausesInfo),
pred_info_set_clauses_info(!.ClausesInfo, !PredInfo),
pred_info_set_typevarset(TypeVarSet, !PredInfo),
pred_info_set_constraint_proof_map(ConstraintProofMap, !PredInfo),
pred_info_set_constraint_map(ConstraintMap, !PredInfo),
% Split the inferred type class constraints into those that apply
% only to the head variables, and those that apply to type variables
% which occur only in the body.
lookup_var_types(InferredVarTypes, HeadVars, ArgTypes),
type_vars_in_types(ArgTypes, ArgTypeVars),
restrict_constraints_to_head_vars(ArgTypeVars,
InferredTypeConstraints0, InferredTypeConstraints,
UnprovenBodyConstraints),
% If there are any as-yet-unproven constraints on type variables
% in the body, then save these in the pred_info. If it turns out that
% this pass was the last pass of type inference, the post_typecheck
% pass will report an error. But we can't report an error now, because
% a later pass of type inference could cause some type variables
% to become bound to types that make the constraints satisfiable,
% causing the error to go away.
pred_info_set_unproven_body_constraints(UnprovenBodyConstraints,
!PredInfo),
(
Inferring = yes,
% We need to infer which of the head variable types must be
% existentially quantified.
infer_existential_types(ArgTypeVars, ExistQVars,
!ExternalTypeParams),
% Now save the information we inferred in the pred_info.
pred_info_set_external_type_params(!.ExternalTypeParams,
!PredInfo),
pred_info_set_arg_types(TypeVarSet, ExistQVars, ArgTypes,
!PredInfo),
pred_info_get_class_context(!.PredInfo, OldTypeConstraints),
pred_info_set_class_context(InferredTypeConstraints, !PredInfo),
% Check if anything changed.
( if
(
% If the argument types and the type constraints are
% identical up to renaming, then nothing has changed.
pred_info_get_tvar_kind_map(!.PredInfo, TVarKindMap),
argtypes_identical_up_to_renaming(TVarKindMap, ExistQVars0,
ArgTypes0, OldTypeConstraints, ExistQVars, ArgTypes,
InferredTypeConstraints)
;
% Promises cannot be called from anywhere. Therefore
% even if the types of their arguments have changed,
% this fact won't affect the type analysis of any other
% predicate.
pred_info_get_goal_type(!.PredInfo, GoalType),
GoalType = goal_for_promise(_)
)
then
NextIteration = next_iteration_is_not_needed
else
NextIteration = next_iteration_is_needed
)
;
Inferring = no,
pred_info_set_external_type_params(!.ExternalTypeParams,
!PredInfo),
pred_info_get_origin(!.PredInfo, Origin0),
% Leave the original argtypes etc., but apply any substitutions
% that map existentially quantified type variables to other
% type vars, and then rename them all to match the new typevarset,
% so that the type variables names match up (e.g. with the type
% variables in the constraint_proofs)
% Apply any type substitutions that map existentially quantified
% type variables to other type vars.
(
ExistQVars0 = [],
% Optimize common case.
ExistQVars1 = [],
ArgTypes1 = ArgTypes0,
PredConstraints1 = PredConstraints,
Origin1 = Origin0
;
ExistQVars0 = [_ | _],
term_subst.apply_renaming_in_vars(ExistTypeRenaming,
ExistQVars0, ExistQVars1),
apply_renaming_to_types(ExistTypeRenaming,
ArgTypes0, ArgTypes1),
apply_renaming_to_univ_exist_constraints(
ExistTypeRenaming, PredConstraints, PredConstraints1),
rename_instance_method_constraints(ExistTypeRenaming,
Origin0, Origin1)
),
% Rename them all to match the new typevarset.
term_subst.apply_renaming_in_vars(TVarRenaming,
ExistQVars1, ExistQVars),
apply_renaming_to_types(TVarRenaming, ArgTypes1,
RenamedOldArgTypes),
apply_renaming_to_univ_exist_constraints(TVarRenaming,
PredConstraints1, RenamedOldConstraints),
rename_instance_method_constraints(TVarRenaming, Origin1, Origin),
% Save the results in the pred_info.
pred_info_set_arg_types(TypeVarSet, ExistQVars, RenamedOldArgTypes,
!PredInfo),
pred_info_set_class_context(RenamedOldConstraints, !PredInfo),
pred_info_set_origin(Origin, !PredInfo),
NextIteration = next_iteration_is_not_needed
),
typecheck_info_get_all_errors(!.Info, !:Specs)
).
% Mark the predicate as a stub, and generate a clause of the form
% <p>(...) :-
% PredName = "<Predname>",
% private_builtin.no_clauses(PredName).
% or
% <p>(...) :-
% PredName = "<Predname>",
% private_builtin.sorry(PredName).
% depending on whether the predicate is part of
% the Mercury standard library or not.
%
:- pred generate_and_add_stub_clause(module_info::in, pred_id::in,
pred_info::in, pred_info::out) is det.
generate_and_add_stub_clause(ModuleInfo, PredId, !PredInfo) :-
some [!ClausesInfo] (
pred_info_get_clauses_info(!.PredInfo, !:ClausesInfo),
!.ClausesInfo = clauses_info(VarSet0, _VarTypes,
_VarTable, RttiVarMaps, TVarNameMap, ArgVec,
_ClausesRep, _ItemNumbers, _ForeignClauses, _SyntaxErrors),
PredPieces = describe_one_pred_name(ModuleInfo, no,
should_module_qualify, [], PredId),
PredName = error_pieces_to_one_line_string(PredPieces),
HeadVars = proc_arg_vector_to_list(ArgVec),
pred_info_get_arg_types(!.PredInfo, ArgTypes),
vartypes_from_corresponding_lists(HeadVars, ArgTypes, VarTypes1),
generate_stub_clause(ModuleInfo, !.PredInfo, PredName, StubClause,
VarSet0, VarSet, VarTypes1, VarTypes),
make_var_table(ModuleInfo, VarSet, VarTypes, VarTable),
set_clause_list([StubClause], ClausesRep),
ItemNumbers = init_clause_item_numbers_comp_gen,
!:ClausesInfo = clauses_info(VarSet, VarTypes, VarTable, RttiVarMaps,
TVarNameMap, ArgVec, ClausesRep, ItemNumbers,
no_foreign_lang_clauses, no_clause_syntax_errors),
pred_info_set_clauses_info(!.ClausesInfo, !PredInfo),
% Mark the predicate as a stub, i.e. record that it originally
% had no clauses.
pred_info_get_markers(!.PredInfo, Markers0),
add_marker(marker_stub, Markers0, Markers),
pred_info_set_markers(Markers, !PredInfo)
).
:- pred generate_stub_clause(module_info::in, pred_info::in,
string::in, clause::out,
prog_varset::in, prog_varset::out, vartypes::in, vartypes::out) is det.
generate_stub_clause(ModuleInfo, PredInfo, PredName, StubClause,
!VarSet, !VarTypes) :-
% Generate `PredName = "<PredName>"'.
varset.new_named_var("PredName", PredNameVar, !VarSet),
add_var_type(PredNameVar, string_type, !VarTypes),
pred_info_get_context(PredInfo, Context),
make_string_const_construction(Context, PredNameVar, PredName, UnifyGoal),
% Generate `private_builtin.no_clauses(PredName)'
% or `private_builtin.sorry(PredName)'
PredModuleName = pred_info_module(PredInfo),
( if mercury_std_library_module_name(PredModuleName) then
CalleeName = "sorry"
else
CalleeName = "no_clauses"
),
generate_plain_call(ModuleInfo, pf_predicate,
mercury_private_builtin_module, CalleeName,
[], [PredNameVar], instmap_delta_bind_no_var, only_mode,
detism_det, purity_pure, [], Context, CallGoal),
% Combine the unification and call into a conjunction.
goal_info_init(Context, GoalInfo),
Body = hlds_goal(conj(plain_conj, [UnifyGoal, CallGoal]), GoalInfo),
StubClause = clause(all_modes, Body, impl_lang_mercury, Context,
[], init_unused_statevar_arg_map, clause_is_not_a_fact).
:- pred rename_instance_method_constraints(tvar_renaming::in,
pred_origin::in, pred_origin::out) is det.
rename_instance_method_constraints(Renaming, Origin0, Origin) :-
( if
Origin0 = origin_user(OriginUser0),
OriginUser0 = user_made_instance_method(PFSymNameArity, Constraints0)
then
Constraints0 = instance_method_constraints(ClassId, InstanceTypes0,
InstanceConstraints0, ClassMethodClassContext0),
apply_renaming_to_types(Renaming, InstanceTypes0,
InstanceTypes),
apply_renaming_to_prog_constraints(Renaming,
InstanceConstraints0, InstanceConstraints),
apply_renaming_to_univ_exist_constraints(Renaming,
ClassMethodClassContext0, ClassMethodClassContext),
Constraints = instance_method_constraints(ClassId,
InstanceTypes, InstanceConstraints, ClassMethodClassContext),
OriginUser = user_made_instance_method(PFSymNameArity, Constraints),
Origin = origin_user(OriginUser)
else
Origin = Origin0
).
% Infer which of the head variable types must be existentially quantified.
%
:- pred infer_existential_types(list(tvar)::in, existq_tvars::out,
external_type_params::in, external_type_params::out) is det.
infer_existential_types(ArgTypeVars, ExistQVars,
ExternalTypeParams0, ExternalTypeParams) :-
% First, infer which of the head variable types must be existentially
% quantified: anything that was inserted into the ExternalTypeParams0 set
% must have been inserted due to an existential type in something we
% called, and thus must be existentially quantified. (Note that concrete
% types are "more general" than existentially quantified types, so we
% prefer to infer a concrete type if we can rather than an
% existential type.)
set.list_to_set(ArgTypeVars, ArgTypeVarsSet),
set.list_to_set(ExternalTypeParams0, ExternalTypeParamsSet),
set.intersect(ArgTypeVarsSet, ExternalTypeParamsSet, ExistQVarsSet),
set.difference(ArgTypeVarsSet, ExistQVarsSet, UnivQVarsSet),
set.to_sorted_list(ExistQVarsSet, ExistQVars),
set.to_sorted_list(UnivQVarsSet, UnivQVars),
% Then we need to insert the universally quantified head variable types
% into the ExternalTypeParams set, which will now contain all the type
% variables that are produced either by stuff we call or by our caller.
% This is needed so that it has the right value when post_typecheck.m
% uses it to check for unbound type variables.
ExternalTypeParams = UnivQVars ++ ExternalTypeParams0.
% restrict_constraints_to_head_vars(HeadVarTypes, Constraints0,
% Constraints, UnprovenConstraints):
%
% Constraints is the subset of Constraints0 which contain no type variables
% other than those in HeadVarTypes. UnprovenConstraints is any unproven
% (universally quantified) type constraints on variables not in
% HeadVarTypes.
%
:- pred restrict_constraints_to_head_vars(list(tvar)::in,
univ_exist_constraints::in, univ_exist_constraints::out,
list(prog_constraint)::out) is det.
restrict_constraints_to_head_vars(ArgVarTypes, !Constraints, UnprovenCs) :-
!.Constraints = univ_exist_constraints(UnivCs0, ExistCs0),
restrict_constraints_to_head_vars_2(ArgVarTypes, UnivCs0, UnivCs,
UnprovenCs),
restrict_constraints_to_head_vars_2(ArgVarTypes, ExistCs0, ExistCs, _),
!:Constraints = univ_exist_constraints(UnivCs, ExistCs).
:- pred restrict_constraints_to_head_vars_2(list(tvar)::in,
list(prog_constraint)::in,
list(prog_constraint)::out, list(prog_constraint)::out) is det.
restrict_constraints_to_head_vars_2(HeadTypeVars, ClassConstraints,
HeadClassConstraints, OtherClassConstraints) :-
list.filter(is_head_class_constraint(HeadTypeVars),
ClassConstraints, HeadClassConstraints, OtherClassConstraints).
:- pred is_head_class_constraint(list(tvar)::in, prog_constraint::in)
is semidet.
is_head_class_constraint(HeadTypeVars, Constraint) :-
Constraint = constraint(_ClassName, ArgTypes),
all [TVar] (
type_list_contains_var(ArgTypes, TVar)
=>
list.member(TVar, HeadTypeVars)
).
% Check whether the argument types, type quantifiers, and type constraints
% are identical up to renaming.
%
% Note that we can't compare each of the parts separately, since we need
% to ensure that the renaming (if any) is consistent over all the arguments
% and all the constraints. So we need to append all the relevant types
% into one big type list and then compare them in a single call
% to identical_up_to_renaming.
%
:- pred argtypes_identical_up_to_renaming(tvar_kind_map::in,
existq_tvars::in, list(mer_type)::in, univ_exist_constraints::in,
existq_tvars::in, list(mer_type)::in, univ_exist_constraints::in)
is semidet.
argtypes_identical_up_to_renaming(KindMap, ExistQVarsA, ArgTypesA,
TypeConstraintsA, ExistQVarsB, ArgTypesB, TypeConstraintsB) :-
constraints_have_same_structure(TypeConstraintsA, TypeConstraintsB,
ConstrainedTypesA, ConstrainedTypesB),
prog_type.var_list_to_type_list(KindMap, ExistQVarsA, ExistQVarTypesA),
prog_type.var_list_to_type_list(KindMap, ExistQVarsB, ExistQVarTypesB),
list.condense([ExistQVarTypesA, ArgTypesA, ConstrainedTypesA], TypesListA),
list.condense([ExistQVarTypesB, ArgTypesB, ConstrainedTypesB], TypesListB),
identical_up_to_renaming(TypesListA, TypesListB).
% Check if two sets of type class constraints have the same structure
% (i.e. they specify the same list of type classes with the same arities)
% and if so, concatenate the argument types for all the type classes
% in each set of type class constraints and return them.
%
:- pred constraints_have_same_structure(
univ_exist_constraints::in, univ_exist_constraints::in,
list(mer_type)::out, list(mer_type)::out) is semidet.
constraints_have_same_structure(ConstraintsA, ConstraintsB, TypesA, TypesB) :-
ConstraintsA = univ_exist_constraints(UnivCsA, ExistCsA),
ConstraintsB = univ_exist_constraints(UnivCsB, ExistCsB),
% these calls to same_length are just an optimization,
% to catch the simple cases quicker
list.same_length(UnivCsA, UnivCsB),
list.same_length(ExistCsA, ExistCsB),
constraints_have_same_structure_loop(UnivCsA, UnivCsB,
UnivTypesA, UnivTypesB),
constraints_have_same_structure_loop(ExistCsA, ExistCsB,
ExistTypesA, ExistTypesB),
TypesA = ExistTypesA ++ UnivTypesA,
TypesB = ExistTypesB ++ UnivTypesB.
:- pred constraints_have_same_structure_loop(
list(prog_constraint)::in, list(prog_constraint)::in,
list(mer_type)::out, list(mer_type)::out) is semidet.
constraints_have_same_structure_loop([], [], [], []).
constraints_have_same_structure_loop(
[ConstraintA | ConstraintsA], [ConstraintB | ConstraintsB],
TypesA, TypesB) :-
ConstraintA = constraint(ClassName, ArgTypesA),
ConstraintB = constraint(ClassName, ArgTypesB),
list.same_length(ArgTypesA, ArgTypesB),
constraints_have_same_structure_loop(ConstraintsA, ConstraintsB,
TypesA0, TypesB0),
TypesA = ArgTypesA ++ TypesA0,
TypesB = ArgTypesB ++ TypesB0.
% A compiler-generated predicate only needs type checking if
% (a) it is a user-defined equality pred, or
% (b) it is the unification or comparison predicate for an existentially
% quantified type.
%
% In case (b), we need to typecheck it to fill in the external_type_params
% field in the pred_info.
%
:- pred special_pred_needs_typecheck(module_info::in, pred_info::in)
is semidet.
special_pred_needs_typecheck(ModuleInfo, PredInfo) :-
% Check if the predicate is a compiler-generated special
% predicate, and if so, for which type.
pred_info_get_origin(PredInfo, Origin),
Origin = origin_compiler(made_for_uci(SpecialPredId, TypeCtor)),
% Check that the special pred isn't one of the builtin types which don't
% have a hlds_type_defn.
not list.member(TypeCtor, builtin_type_ctors_with_no_hlds_type_defn),
% Check whether that type is a type for which there is a user-defined
% equality predicate, or which is existentially typed.
module_info_get_type_table(ModuleInfo, TypeTable),
lookup_type_ctor_defn(TypeTable, TypeCtor, TypeDefn),
hlds_data.get_type_defn_body(TypeDefn, Body),
special_pred_for_type_needs_typecheck(ModuleInfo, SpecialPredId, Body).
%---------------------------------------------------------------------------%
:- pred typecheck_check_remaining_coercion_constraints(type_assign_set::in,
typecheck_info::in, typecheck_info::out) is det.
typecheck_check_remaining_coercion_constraints(TypeAssignSet, !Info) :-
(
TypeAssignSet = [],
unexpected($pred, "no type-assignment")
;
TypeAssignSet = [TypeAssign],
type_assign_get_coerce_constraints(TypeAssign, Coercions),
list.foldl(report_coercion(TypeAssign), Coercions, !Info)
;
TypeAssignSet = [_, _ | _]
% If there are multiple type assignments then there is a type ambiguity
% error anyway. Reporting invalid coercions from different type
% assignments would be confusing.
).
:- pred report_coercion(type_assign::in, coerce_constraint::in,
typecheck_info::in, typecheck_info::out) is det.
report_coercion(TypeAssign, Coercion, !Info) :-
% XXX When inferring types for a predicate/function with no declared type,
% we should not report coercions as invalid until the argument types have
% been inferred.
Coercion = coerce_constraint(FromType0, ToType0, Context, FromVar, Status),
type_assign_get_typevarset(TypeAssign, TVarSet),
type_assign_get_type_bindings(TypeAssign, TypeBindings),
apply_rec_subst_to_type(TypeBindings, FromType0, FromType),
apply_rec_subst_to_type(TypeBindings, ToType0, ToType),
typecheck_info_get_error_clause_context(!.Info, ClauseContext),
(
Status = need_to_check,
unexpected($pred, "need to check")
;
Status = unsatisfiable,
Spec = report_invalid_coerce_from_to(ClauseContext, Context, FromVar,
TVarSet, FromType, ToType)
;
Status = not_yet_resolved,
Spec = report_unresolved_coerce_from_to(ClauseContext, Context,
FromVar, TVarSet, FromType, ToType)
;
Status = satisfied_but_redundant,
Spec = report_redundant_coerce(ClauseContext, Context, FromVar,
TVarSet, FromType)
),
typecheck_info_add_error(Spec, !Info).
%---------------------------------------------------------------------------%
:- end_module check_hlds.typecheck.
%---------------------------------------------------------------------------%