Skip to content

Commit 28678e7

Browse files
authored
Merge pull request #2738 from adri326/fix-2275-dcgs-call-module
Fix dcgs using call(M:Pred) when M was left unassigned
2 parents f69c58c + b76bdd7 commit 28678e7

File tree

9 files changed

+88
-14
lines changed

9 files changed

+88
-14
lines changed

src/lib/builtins.pl

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,7 +1175,7 @@
11751175
% Asserts (inserts) a new clause (rule or fact) into the current module.
11761176
% The clause will be inserted at the beginning of the module.
11771177
asserta(Clause0) :-
1178-
loader:strip_subst_module(Clause0, user, Module, Clause),
1178+
loader:strip_module(Clause0, Module, Clause),
11791179
asserta_(Module, Clause).
11801180

11811181
asserta_(Module, (Head :- Body)) :-
@@ -1191,7 +1191,7 @@
11911191
% Asserts (inserts) a new clause (rule or fact) into the current module.
11921192
% The clase will be inserted at the end of the module.
11931193
assertz(Clause0) :-
1194-
loader:strip_subst_module(Clause0, user, Module, Clause),
1194+
loader:strip_module(Clause0, Module, Clause),
11951195
assertz_(Module, Clause).
11961196

11971197
assertz_(Module, (Head :- Body)) :-
@@ -1211,15 +1211,9 @@
12111211
loader:strip_module(Clause0, Module, Clause),
12121212
( Clause \= (_ :- _) ->
12131213
loader:strip_module(Clause, Module, Head),
1214-
( var(Module) -> Module = user
1215-
; true
1216-
),
12171214
Body = true,
12181215
retract_module_clause(Head, Body, Module)
12191216
; Clause = (Head :- Body) ->
1220-
( var(Module) -> Module = user
1221-
; true
1222-
),
12231217
retract_module_clause(Head, Body, Module)
12241218
).
12251219

@@ -1374,10 +1368,6 @@
13741368
'$get_db_refs'(_, _, _, PIs),
13751369
lists:member(Pred, PIs)
13761370
; loader:strip_module(Pred, Module, UnqualifiedPred),
1377-
( var(Module),
1378-
\+ functor(Pred, (:), 2)
1379-
; atom(Module)
1380-
),
13811371
UnqualifiedPred = Name/Arity ->
13821372
( ( nonvar(Name), \+ atom(Name)
13831373
; nonvar(Arity), \+ integer(Arity)

src/loader.pl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -695,10 +695,9 @@
695695
( MQ = specified(M) ->
696696
true
697697
; MQ = unspecified,
698-
true
698+
load_context(M)
699699
).
700700

701-
702701
:- non_counted_backtracking strip_subst_module/4.
703702

704703
strip_subst_module(Goal, M1, M2, G) :-

src/machine/system_calls.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,22 @@ use super::libraries;
104104
use super::preprocessor::to_op_decl;
105105
use super::preprocessor::to_op_decl_spec;
106106

107+
/// Represents the presence (or absence) of a `module:` prefix to predicates, used to
108+
/// refer to predicates defined in a given `module` that haven't been imported
109+
/// (through `use_module/1`) or exported.
110+
///
111+
/// On the Rust side, [`MachineState::strip_module`] splits a given [`HeapCellValue`] into
112+
/// a pair of [`ModuleQuantification`] and `HeapCellValue`.
113+
///
114+
/// On the Prolog side, `strip_module(X, Y, Z)` is a wrapper around [`MachineState::strip_module`],
115+
/// which takes care of splitting the `X = module:predicate` pair into `Y = module` and
116+
/// `Z = predicate`. If no module prefix is present (ie. [`MachineState::strip_module`] returned
117+
/// `Unspecified`), then `strip_module/3` calls `load_context(Y)`, unifying `Y` with the currently
118+
/// loaded module (or `user`).
119+
///
120+
/// [`Machine::quantification_to_module_name`] provides a similar mechanism on the Rust side to
121+
/// obtain the currently loaded module in the `Unspecified` case.
122+
/// It also defaults to `user`, for instance if we are in the REPL.
107123
#[derive(Debug)]
108124
pub(crate) enum ModuleQuantification {
109125
Specified(HeapCellValue),

src/tests/module_resolution.pl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
:- module(module_resolution, [get_module/2]).
2+
3+
get_module(P, M) :- strip_module(P, M, _).
4+
5+
:- initialization((strip_module(hello, M, _), write(M), write('\n'))).
6+
:- initialization((loader:strip_module(hello, M, _), write(M), write('\n'))).
7+
:- initialization((get_module(hello, M), write(M), write('\n'))).
8+
:- initialization((module_resolution:get_module(hello, M), write(M), write('\n'))).

tests-pl/issue2725.pl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
:- module(issue2725, []).
2+
:- use_module(library(dcgs)).
3+
4+
% Tests that the id/3 dcg can be called.
5+
% library(dcgs) currently expands it to id(X, Y, Z) :- phrase(X, Y, Z).
6+
id(X) --> X.
7+
call_id :-
8+
id("Hello", X, []),
9+
X = "Hello".
10+
:- initialization(call_id).
11+
12+
test_default_strip_module :-
13+
strip_module(hello, M, P),
14+
nonvar(M),
15+
M = issue2725,
16+
nonvar(P),
17+
P = hello,
18+
strip_module(hello, issue2725, _),
19+
strip_module(hello, M, P).
20+
:- initialization(test_default_strip_module).
21+
22+
% Tests that strip_module followed by call works with or without the module: prefix.
23+
strip_module_call(Pred) :-
24+
loader:strip_module(Pred, M, Pred0),
25+
call(M:Pred0).
26+
27+
my_true.
28+
29+
test_strip_module_call :-
30+
strip_module_call(my_true),
31+
strip_module_call(issue2725:my_true).
32+
:- initialization(test_strip_module_call).
33+
34+
% :- initialization(loader:prolog_load_context(module, M), write(M), write('\n')).
35+
% :- initialization(loader:load_context(user)).

tests/scryer/cli/src_tests/module_resolution.stderr

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module_resolution
2+
module_resolution
3+
module_resolution
4+
module_resolution
5+
user
6+
user
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
args = [
2+
"-f",
3+
"--no-add-history",
4+
"src/tests/module_resolution.pl",
5+
"-f",
6+
"-g", "use_module(library(module_resolution))",
7+
"-g", "get_module(some_predicate, M), write(M), write('\\n')",
8+
"-g", "module_resolution:get_module(some_predicate, M), write(M), write('\\n')",
9+
"-g", "halt"
10+
]

tests/scryer/issues.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,13 @@ fn call_qualification() {
3333
fn load_context_unreachable() {
3434
load_module_test("tests-pl/load-context-unreachable.pl", "");
3535
}
36+
37+
// Issue #2725: A dcg of the form `id(X) --> X.` would previously trigger an instantiation
38+
// error, as it would call `strip_module(X, M, P)` and later `call(M:P)`,
39+
// but `strip_module` left `M` uninstanciated if the `module:` prefix was unspecified.
40+
#[serial]
41+
#[test]
42+
#[cfg_attr(miri, ignore = "it takes too long to run")]
43+
fn issue2725_dcg_without_module() {
44+
load_module_test("tests-pl/issue2725.pl", "");
45+
}

0 commit comments

Comments
 (0)