-
Notifications
You must be signed in to change notification settings - Fork 54
/
check_promise.m
398 lines (361 loc) · 16.9 KB
/
check_promise.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2015 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.
%---------------------------------------------------------------------------%
%
% This module checks that exported promises refer only to exported entities,
% and stores each kind of promise in its own table.
%
%---------------------------------------------------------------------------%
:- module check_hlds.check_promise.
:- interface.
:- import_module hlds.
:- import_module hlds.hlds_module.
:- import_module parse_tree.
:- import_module parse_tree.error_spec.
:- import_module io.
:- import_module list.
:- pred check_promises_in_module(io.text_output_stream::in,
module_info::in, module_info::out,
list(error_spec)::in, list(error_spec)::out) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module hlds.assertion.
:- import_module hlds.goal_util.
:- import_module hlds.hlds_clauses.
:- import_module hlds.hlds_data.
:- import_module hlds.hlds_goal.
:- import_module hlds.hlds_pred.
:- import_module hlds.hlds_promise.
:- import_module hlds.passes_aux.
:- import_module hlds.status.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.prog_data.
:- import_module parse_tree.prog_type.
:- import_module parse_tree.var_table.
:- import_module bool.
:- import_module require.
%---------------------------------------------------------------------------%
check_promises_in_module(ProgressStream, !ModuleInfo, !Specs) :-
module_info_get_valid_pred_ids(!.ModuleInfo, ValidPredIds0),
check_promises_in_preds(ProgressStream, ValidPredIds0,
[], ToInvalidatePredIds, !ModuleInfo, !Specs),
module_info_make_pred_ids_invalid(ToInvalidatePredIds, !ModuleInfo).
:- pred check_promises_in_preds(io.text_output_stream::in, list(pred_id)::in,
list(pred_id)::in, list(pred_id)::out,
module_info::in, module_info::out,
list(error_spec)::in, list(error_spec)::out) is det.
check_promises_in_preds(_, [], !ToInvalidatePredIds, !ModuleInfo, !Specs).
check_promises_in_preds(ProgressStream, [PredId | PredIds],
!ToInvalidatePredIds, !ModuleInfo, !Specs) :-
check_promises_in_pred(ProgressStream, PredId,
!ToInvalidatePredIds, !ModuleInfo, !Specs),
check_promises_in_preds(ProgressStream, PredIds,
!ToInvalidatePredIds, !ModuleInfo, !Specs).
% If the given predicate is a promise, this predicate records that promise
% in the relevant promise table (which will be either the assertion table
% or the promise_ex table). It then also marks the predicate for deletion
% from the list of the pred ids that future compiler passes should process.
%
% If the assertion is in the interface, we check that it doesn't refer
% to any symbols which are local to that module.
%
:- pred check_promises_in_pred(io.text_output_stream::in, pred_id::in,
list(pred_id)::in, list(pred_id)::out,
module_info::in, module_info::out,
list(error_spec)::in, list(error_spec)::out) is det.
check_promises_in_pred(ProgressStream, PredId, !ToInvalidatePredIds,
!ModuleInfo, !Specs) :-
module_info_pred_info(!.ModuleInfo, PredId, PredInfo),
pred_info_get_goal_type(PredInfo, GoalType),
(
GoalType = goal_for_promise(PromiseType),
( if pred_info_is_imported(PredInfo) then
% We won't have run typechecking on this predicate. This means that
% the pred_ids and proc_ids fields in plain_call goals won't be
% filled in, which means store_promise cannot do its job.
true
else
trace [io(!IO)] (
maybe_write_pred_progress_message(ProgressStream, !.ModuleInfo,
"Checking promises in", PredId, !IO)
),
% Store the declaration in the appropriate table and get the goal
% for the promise.
store_promise(PredId, PredInfo, PromiseType, !ModuleInfo, Goal),
!:ToInvalidatePredIds = [PredId | !.ToInvalidatePredIds],
( if pred_info_is_exported(PredInfo) then
check_in_interface_promise_goal(!.ModuleInfo, PredInfo, Goal,
!Specs)
else
true
)
)
;
GoalType = goal_not_for_promise(_)
).
%---------------------%
% Store promise declaration, normalise goal and return new module_info
% and the goal for further processing.
%
:- pred store_promise(pred_id::in, pred_info::in, promise_type::in,
module_info::in, module_info::out, hlds_goal::out) is det.
store_promise(PredId, PredInfo, PromiseType, !ModuleInfo, Goal) :-
(
% Assertions.
% XXX PROMISE Before we add the assertion to the assertion table,
% we should check that Goal has one of the forms that we recognize.
% If it does not, we should generate an error message.
PromiseType = promise_type_true,
module_info_get_assertion_table(!.ModuleInfo, AssertTable0),
assertion_table_add_assertion(PredId, AssertionId,
AssertTable0, AssertTable),
module_info_set_assertion_table(AssertTable, !ModuleInfo),
assertion.assert_id_goal(!.ModuleInfo, AssertionId, Goal),
assertion.record_preds_used_in(Goal, AssertionId, !ModuleInfo)
;
% Exclusivity promises.
% XXX PROMISE Here we record the fact that CalleePredIds are in
% an exclusivity promise, but
% - we do not record which arguments of each predicate have to match
% for the promise to apply, or
% - we do not record whether the promise is for exhaustiveness as well.
% If we *did* record a flag to distinguish between the two, it could
% handle promise_type_exclusive as well.
%
% However, the above would matter only if we started to 't *use*
% the exclusive table.
( PromiseType = promise_type_exclusive
; PromiseType = promise_type_exclusive_exhaustive
),
get_promise_ex_goal(PredInfo, Goal),
pred_ids_called_from_goal(Goal, CalleePredIds),
module_info_get_exclusive_table(!.ModuleInfo, Table0),
exclusive_table_add_exclusive(PredId, CalleePredIds, Table0, Table),
module_info_set_exclusive_table(Table, !ModuleInfo)
;
% Exhaustiveness promises -- XXX not yet implemented.
PromiseType = promise_type_exhaustive,
get_promise_ex_goal(PredInfo, Goal)
).
% Get the goal from a promise_ex declaration.
%
% XXX PROMISE: duplicate code: assert_id_goal in assertion.m
% does the same thing.
%
:- pred get_promise_ex_goal(pred_info::in, hlds_goal::out) is det.
get_promise_ex_goal(PredInfo, Goal) :-
pred_info_get_clauses_info(PredInfo, ClausesInfo),
clauses_info_get_clauses_rep(ClausesInfo, ClausesRep, _ItemNumbers),
get_clause_list_maybe_repeated(ClausesRep, Clauses),
( if Clauses = [Clause] then
Goal0 = Clause ^ clause_body,
assertion.normalise_goal(Goal0, Goal)
else
unexpected($pred, "not a single clause")
).
%---------------------%
% Ensure that an assertion which is defined in an interface doesn't
% refer to any constructors, functions and predicates defined in the
% implementation of that module.
%
:- pred check_in_interface_promise_goal(module_info::in, pred_info::in,
hlds_goal::in, list(error_spec)::in, list(error_spec)::out) is det.
check_in_interface_promise_goal(ModuleInfo, PredInfo, Goal, !Specs) :-
Goal = hlds_goal(GoalExpr, GoalInfo),
(
GoalExpr = plain_call(PredId, _, _, _, _, _),
check_in_interface_promise_call(ModuleInfo, PredId, GoalInfo, !Specs)
;
GoalExpr = generic_call(_, _, _, _, _)
;
GoalExpr = unify(Var, RHS, _, _, _),
Context = goal_info_get_context(GoalInfo),
check_in_interface_promise_unify_rhs(ModuleInfo, PredInfo, Var, RHS,
Context, !Specs)
;
GoalExpr = call_foreign_proc(_, PredId, _, _, _, _, _),
% XXX How can there be a call_foreign_proc in a promise?
check_in_interface_promise_call(ModuleInfo, PredId, GoalInfo, !Specs)
;
GoalExpr = conj(_, Goals),
check_in_interface_promise_goals(ModuleInfo, PredInfo, Goals, !Specs)
;
GoalExpr = switch(_, _, _),
unexpected($pred, "assertion contains switch")
;
GoalExpr = disj(Goals),
check_in_interface_promise_goals(ModuleInfo, PredInfo, Goals, !Specs)
;
GoalExpr = negation(SubGoal),
check_in_interface_promise_goal(ModuleInfo, PredInfo, SubGoal, !Specs)
;
GoalExpr = scope(_, SubGoal),
check_in_interface_promise_goal(ModuleInfo, PredInfo, SubGoal, !Specs)
;
GoalExpr = if_then_else(_, Cond, Then, Else),
check_in_interface_promise_goal(ModuleInfo, PredInfo, Cond, !Specs),
check_in_interface_promise_goal(ModuleInfo, PredInfo, Then, !Specs),
check_in_interface_promise_goal(ModuleInfo, PredInfo, Else, !Specs)
;
GoalExpr = shorthand(ShortHand),
(
ShortHand = atomic_goal(_, _, _, _, MainGoal, OrElseGoals, _),
check_in_interface_promise_goal(ModuleInfo, PredInfo, MainGoal,
!Specs),
check_in_interface_promise_goals(ModuleInfo, PredInfo, OrElseGoals,
!Specs)
;
ShortHand = try_goal(_, _, SubGoal),
check_in_interface_promise_goal(ModuleInfo, PredInfo, SubGoal,
!Specs)
;
ShortHand = bi_implication(LHS, RHS),
check_in_interface_promise_goal(ModuleInfo, PredInfo, LHS, !Specs),
check_in_interface_promise_goal(ModuleInfo, PredInfo, RHS, !Specs)
)
).
:- pred check_in_interface_promise_call(module_info::in, pred_id::in,
hlds_goal_info::in, list(error_spec)::in, list(error_spec)::out) is det.
check_in_interface_promise_call(ModuleInfo, PredId, GoalInfo, !Specs) :-
module_info_get_name(ModuleInfo, ModuleName),
module_info_pred_info(ModuleInfo, PredId, PredInfo),
pred_info_get_module_name(PredInfo, PredModuleName),
pred_info_get_name(PredInfo, PredName),
pred_info_get_status(PredInfo, PredStatus),
( if ModuleName = PredModuleName then
DefnInImplSection =
pred_status_defined_in_impl_section(PredStatus),
(
DefnInImplSection = yes,
Context = goal_info_get_context(GoalInfo),
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
PredSymName = qualified(PredModuleName, PredName),
user_arity(UserArityInt) = pred_info_user_arity(PredInfo),
SymNameArity = sym_name_arity(PredSymName, UserArityInt),
InitIdPieces = [p_or_f(PredOrFunc)],
SubjectIdPieces = [qual_sym_name_arity(SymNameArity)],
report_assertion_interface_error(ModuleName, Context,
InitIdPieces, SubjectIdPieces, !Specs)
;
DefnInImplSection = no
)
else
Context = goal_info_get_context(GoalInfo),
PredOrFunc = pred_info_is_pred_or_func(PredInfo),
PredSymName = qualified(PredModuleName, PredName),
user_arity(UserArityInt) = pred_info_user_arity(PredInfo),
SymNameArity = sym_name_arity(PredSymName, UserArityInt),
InitIdPieces = [p_or_f(PredOrFunc)],
SubjectIdPieces = [qual_sym_name_arity(SymNameArity)],
report_assertion_module_error(ModuleName, Context, PredModuleName,
InitIdPieces, SubjectIdPieces, !Specs)
).
:- pred check_in_interface_promise_unify_rhs(module_info::in, pred_info::in,
prog_var::in, unify_rhs::in, prog_context::in,
list(error_spec)::in, list(error_spec)::out) is det.
check_in_interface_promise_unify_rhs(ModuleInfo, PredInfo, Var, RHS, Context,
!Specs) :-
(
RHS = rhs_var(_)
;
RHS = rhs_functor(ConsId, _, _),
pred_info_get_clauses_info(PredInfo, ClausesInfo),
clauses_info_get_var_table(ClausesInfo, VarTable),
lookup_var_type(VarTable, Var, Type),
type_to_ctor_det(Type, TypeCtor),
module_info_get_type_table(ModuleInfo, TypeTable),
lookup_type_ctor_defn(TypeTable, TypeCtor, TypeDefn),
get_type_defn_status(TypeDefn, TypeStatus),
module_info_get_name(ModuleInfo, ModuleName),
TypeCtor = type_ctor(TypeCtorSymName, _),
( if sym_name_get_module_name(TypeCtorSymName, TypeCtorModuleName) then
( if ModuleName = TypeCtorModuleName then
DefinedInImpl =
type_status_defined_in_impl_section(TypeStatus),
(
DefinedInImpl = yes,
InitIdPieces = [words("constructor")],
SubjectIdPieces = [qual_cons_id_and_maybe_arity(ConsId)],
report_assertion_interface_error(ModuleName, Context,
InitIdPieces, SubjectIdPieces, !Specs)
;
DefinedInImpl = no
)
else
InitIdPieces = [words("constructor")],
SubjectIdPieces = [qual_cons_id_and_maybe_arity(ConsId)],
report_assertion_module_error(ModuleName, Context,
TypeCtorModuleName, InitIdPieces, SubjectIdPieces, !Specs)
)
else
% TypeCtorSymName has no module name component, so it must be
% a builtin type constructor. If we had a table that mapped each
% builtin type_ctor to the name of the module(s) that could make
% promises about that type_ctor, we could check that here,
% but we don't have such a table.
true
)
;
RHS = rhs_lambda_goal(_, _, _, _, _, _, Goal),
check_in_interface_promise_goal(ModuleInfo, PredInfo, Goal, !Specs)
).
:- pred check_in_interface_promise_goals(module_info::in, pred_info::in,
list(hlds_goal)::in, list(error_spec)::in, list(error_spec)::out) is det.
check_in_interface_promise_goals(_ModuleInfo, _PredInfo, [], !Specs).
check_in_interface_promise_goals(ModuleInfo, PredInfo, [Goal0 | Goal0s],
!Specs) :-
check_in_interface_promise_goal(ModuleInfo, PredInfo, Goal0, !Specs),
check_in_interface_promise_goals(ModuleInfo, PredInfo, Goal0s, !Specs).
%---------------------%
:- pred report_assertion_interface_error(module_name::in, prog_context::in,
list(format_piece)::in, list(format_piece)::in,
list(error_spec)::in, list(error_spec)::out) is det.
report_assertion_interface_error(ModuleName, Context,
InitIdPieces, SubjectIdPieces, !Specs) :-
MainPieces =
[words("In interface for module"), qual_sym_name(ModuleName),
suffix(":"), nl,
words("error: this exported promise refers to")] ++
InitIdPieces ++ color_as_subject(SubjectIdPieces) ++ [suffix(","),
words("which is")] ++
color_as_incorrect([words("not exported")]) ++
[words("from module"), qual_sym_name(ModuleName), suffix("."), nl],
VerbosePieces =
[words("Either move the promise into the implementation section,"),
words("or move the definition into the interface."), nl],
Msgs = [always(MainPieces), verbose_only(verbose_always, VerbosePieces)],
Spec = error_spec($pred, severity_error, phase_type_check,
[simple_msg(Context, Msgs)]),
!:Specs = [Spec | !.Specs].
:- pred report_assertion_module_error(module_name::in, prog_context::in,
module_name::in, list(format_piece)::in, list(format_piece)::in,
list(error_spec)::in, list(error_spec)::out) is det.
report_assertion_module_error(ModuleName, Context, PredModuleName,
InitIdPieces, SubjectIdPieces, !Specs) :-
MainPieces =
[words("In interface for module"), qual_sym_name(ModuleName),
suffix(":"), nl,
words("error: exported promise refers to")] ++
InitIdPieces ++ color_as_subject(SubjectIdPieces) ++ [suffix(","),
words("which is")] ++
color_as_incorrect([words("defined in another module,"),
qual_sym_name(PredModuleName), suffix(".")]) ++
[nl],
VerbosePieces =
[words("Either move the promise into the implementation section,"),
words("or move it to the"),
qual_sym_name(PredModuleName), words("module."),
words("In most cases, the latter is preferable."), nl],
Msgs = [always(MainPieces), verbose_only(verbose_always, VerbosePieces)],
Spec = error_spec($pred, severity_error, phase_type_check,
[simple_msg(Context, Msgs)]),
!:Specs = [Spec | !.Specs].
%---------------------------------------------------------------------------%
:- end_module check_hlds.check_promise.
%---------------------------------------------------------------------------%