Skip to content

Commit db08650

Browse files
committed
feature: warn if modules is missing any mentioned modules
We warn the user if modules_without_implementation, private_modules or virtual_modules contains any modules not in the modules field. Fixes #7026 This will be made into an error in 3.9. Signed-off-by: Ali Caglayan <alizter@gmail.com>
1 parent 03313a1 commit db08650

File tree

7 files changed

+164
-42
lines changed

7 files changed

+164
-42
lines changed

CHANGES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ Unreleased
2020
- Use `$PKG_CONFIG`, when set, to find the `pkg-config` binary (#7469, fixes
2121
#2572, @anmonteiro)
2222

23+
- Modules that were declared in `(modules_without_implementation)`,
24+
`(private_modules)` or `(virtual_modules)` but not declared in `(modules)`
25+
will cause Dune to emit a warning which will become an error in 3.9. (#7608,
26+
fixes #7026, @Alizter)
27+
2328
- Preliminary support for Coq compiled intefaces (`.vos` files) enabled via
2429
`(mode vos)` in `coq.theory` stanzas. This can be used in combination with
2530
`dune coq top` to obtain fast re-building of dependencies (with no checking

src/dune_rules/ml_sources.ml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ let virtual_modules ~lookup_vlib vlib =
287287
}
288288

289289
let make_lib_modules ~dir ~libs ~lookup_vlib ~(lib : Library.t) ~modules
290+
~is_vendored
290291
~include_subdirs:
291292
(loc_include_subdirs, (include_subdirs : Dune_file.Include_subdirs.t)) =
292293
let open Resolve.Memo.O in
@@ -338,7 +339,7 @@ let make_lib_modules ~dir ~libs ~lookup_vlib ~(lib : Library.t) ~modules
338339
let { Buildable.loc = stanza_loc; modules = modules_settings; _ } =
339340
lib.buildable
340341
in
341-
Modules_field_evaluator.eval ~modules ~stanza_loc ~kind
342+
Modules_field_evaluator.eval ~modules ~stanza_loc ~kind ~is_vendored
342343
~private_modules:
343344
(Option.value ~default:Ordered_set_lang.standard lib.private_modules)
344345
~src_dir:dir modules_settings
@@ -371,7 +372,7 @@ let make_lib_modules ~dir ~libs ~lookup_vlib ~(lib : Library.t) ~modules
371372
~modules ~main_module_name ~wrapped )
372373

373374
let modules_of_stanzas dune_file ~dir ~scope ~lookup_vlib ~modules
374-
~include_subdirs =
375+
~include_subdirs ~is_vendored =
375376
let rev_filter_partition =
376377
let rec loop l (acc : Modules.groups) =
377378
match l with
@@ -406,7 +407,7 @@ let modules_of_stanzas dune_file ~dir ~scope ~lookup_vlib ~modules
406407
let+ sources, modules =
407408
let lookup_vlib = lookup_vlib ~loc:lib.buildable.loc in
408409
make_lib_modules ~dir ~libs:(Scope.libs scope) ~lookup_vlib ~modules
409-
~lib ~include_subdirs
410+
~lib ~include_subdirs ~is_vendored
410411
>>= Resolve.read_memo
411412
in
412413
let obj_dir = Library.obj_dir lib ~dir in
@@ -417,10 +418,9 @@ let modules_of_stanzas dune_file ~dir ~scope ~lookup_vlib ~modules
417418
let { Buildable.loc = stanza_loc; modules = modules_settings; _ } =
418419
exes.buildable
419420
in
420-
Modules_field_evaluator.eval ~modules ~stanza_loc
421-
~kind:Modules_field_evaluator.Exe_or_normal_lib
422-
~private_modules:Ordered_set_lang.standard ~src_dir:dir
423-
modules_settings
421+
Modules_field_evaluator.eval ~modules ~stanza_loc ~src_dir:dir
422+
~kind:Modules_field_evaluator.Exe_or_normal_lib ~is_vendored
423+
~private_modules:Ordered_set_lang.standard modules_settings
424424
in
425425
let modules =
426426
let project = Scope.project scope in
@@ -434,7 +434,7 @@ let modules_of_stanzas dune_file ~dir ~scope ~lookup_vlib ~modules
434434
let obj_dir = Obj_dir.make_melange_emit ~dir ~name:mel.target in
435435
let sources, modules =
436436
Modules_field_evaluator.eval ~modules ~stanza_loc:mel.loc
437-
~kind:Modules_field_evaluator.Exe_or_normal_lib
437+
~kind:Modules_field_evaluator.Exe_or_normal_lib ~is_vendored
438438
~private_modules:Ordered_set_lang.standard ~src_dir:dir mel.modules
439439
in
440440
let modules =
@@ -449,6 +449,11 @@ let make dune_file ~dir ~scope ~lib_config ~loc ~lookup_vlib
449449
~include_subdirs:
450450
(loc_include_subdirs, (include_subdirs : Dune_file.Include_subdirs.t))
451451
~dirs =
452+
let* is_vendored =
453+
match Path.Build.drop_build_context dir with
454+
| Some src_dir -> Source_tree.is_vendored src_dir
455+
| None -> Memo.return false
456+
in
452457
let+ modules_of_stanzas =
453458
let modules =
454459
let dialects = Dune_project.dialects (Scope.project scope) in
@@ -503,6 +508,7 @@ let make dune_file ~dir ~scope ~lib_config ~loc ~lookup_vlib
503508
in
504509
modules_of_stanzas dune_file ~dir ~scope ~lookup_vlib ~modules
505510
~include_subdirs:(loc_include_subdirs, include_subdirs)
511+
~is_vendored
506512
in
507513
let modules = Modules.make modules_of_stanzas in
508514
let artifacts =

src/dune_rules/modules_field_evaluator.ml

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ type single_module_error =
6060
| Vmodule_impl_missing_impl
6161
| Forbidden_new_public_module
6262
| Vmodule_impls_with_own_intf
63+
| Undeclared_module_without_implementation
64+
| Undeclared_private_module
65+
| Undeclared_virtual_module
6366

6467
type errors =
6568
{ errors : (single_module_error * Loc.t * Module_name.Path.t) list
@@ -97,14 +100,19 @@ let find_errors ~modules ~intf_only ~virtual_modules ~private_modules
97100
in
98101
let ( ++ ) f g loc acc = f loc (g loc acc) in
99102
let ( !? ) = Option.is_some in
100-
with_property private_ (add_if impl_vmodule Private_impl_of_vmodule)
103+
with_property private_
104+
(add_if impl_vmodule Private_impl_of_vmodule
105+
++ add_if (not !?modules) Undeclared_private_module)
101106
@@ with_property intf_only
102107
(add_if has_impl Spurious_module_intf
103-
++ add_if impl_vmodule Vmodule_impl_intf_only_exclusion)
108+
++ add_if impl_vmodule Vmodule_impl_intf_only_exclusion
109+
++ add_if (not !?modules) Undeclared_module_without_implementation
110+
)
104111
@@ with_property virtual_
105112
(add_if has_impl Spurious_module_virtual
106113
++ add_if !?intf_only Virt_intf_overlap
107-
++ add_if !?private_ Private_virt_module)
114+
++ add_if !?private_ Private_virt_module
115+
++ add_if (not !?modules) Undeclared_virtual_module)
108116
@@ with_property modules
109117
(add_if
110118
((not !?private_)
@@ -128,7 +136,7 @@ let find_errors ~modules ~intf_only ~virtual_modules ~private_modules
128136

129137
let check_invalid_module_listing ~stanza_loc ~modules_without_implementation
130138
~intf_only ~modules ~virtual_modules ~private_modules
131-
~existing_virtual_modules ~allow_new_public_modules =
139+
~existing_virtual_modules ~allow_new_public_modules ~is_vendored =
132140
let { errors; unimplemented_virt_modules } =
133141
find_errors ~modules ~intf_only ~virtual_modules ~private_modules
134142
~existing_virtual_modules ~allow_new_public_modules
@@ -154,18 +162,24 @@ let check_invalid_module_listing ~stanza_loc ~modules_without_implementation
154162
let missing_intf_only = get Missing_intf_only in
155163
let spurious_modules_intf = get Spurious_module_intf in
156164
let spurious_modules_virtual = get Spurious_module_virtual in
165+
let undeclared_modules_without_implementation =
166+
get Undeclared_module_without_implementation
167+
in
168+
let undeclared_private_modules = get Undeclared_private_module in
169+
let undeclared_virtual_modules = get Undeclared_virtual_module in
157170
let uncapitalized =
158171
List.map ~f:(fun (_, m) -> Module_name.Path.uncapitalize m)
159172
in
160173
let line_list modules =
161174
Pp.enumerate modules ~f:(fun (_, m) ->
162175
Pp.verbatim (Module_name.Path.to_string m))
163176
in
164-
let print before l after =
177+
let print ?(is_error = true) before l after =
165178
match l with
166179
| [] -> ()
167180
| (loc, _) :: _ ->
168-
User_error.raise ~loc (List.concat [ before; [ line_list l ]; after ])
181+
User_warning.emit ~is_error ~loc
182+
(List.concat [ before; [ line_list l ]; after ])
169183
in
170184
print
171185
[ Pp.text "The following modules are implementations of virtual modules:"
@@ -213,6 +227,21 @@ let check_invalid_module_listing ~stanza_loc ~modules_without_implementation
213227
(unimplemented_virt_modules |> Module_name.Path.Set.to_list
214228
|> List.map ~f:(fun name -> (stanza_loc, name)))
215229
[ Pp.text "You must provide an implementation for all of these modules." ];
230+
(* Checking that (modules) incldues all declared modules *)
231+
let print_undelared_modules field mods =
232+
(* TODO: this is a warning for now, change to an error in 3.9. *)
233+
(* If we are in a vendored stanza we do nothing. *)
234+
if is_vendored then ()
235+
else
236+
print ~is_error:false
237+
[ Pp.textf "These modules appear in the %s field:" field ]
238+
mods
239+
[ Pp.text "They must also appear in the modules field." ]
240+
in
241+
print_undelared_modules "modules_without_implementation"
242+
undeclared_modules_without_implementation;
243+
print_undelared_modules "private_modules" undeclared_private_modules;
244+
print_undelared_modules "virtual_modules" undeclared_virtual_modules;
216245
(if missing_intf_only <> [] then
217246
match Ordered_set_lang.loc modules_without_implementation with
218247
| None ->
@@ -263,7 +292,7 @@ let eval0 ~modules:(all_modules : Module.Source.t Module_trie.t) ~stanza_loc
263292
{ modules; fake_modules = !fake_modules }
264293

265294
let eval ~modules:(all_modules : Module.Source.t Module_trie.t) ~stanza_loc
266-
~private_modules ~kind ~src_dir
295+
~private_modules ~kind ~src_dir ~is_vendored
267296
{ Stanza_common.Modules_settings.modules = _
268297
; root_module
269298
; modules_without_implementation
@@ -299,7 +328,7 @@ let eval ~modules:(all_modules : Module.Source.t Module_trie.t) ~stanza_loc
299328
]);
300329
check_invalid_module_listing ~stanza_loc ~modules_without_implementation
301330
~intf_only ~modules ~virtual_modules ~private_modules
302-
~existing_virtual_modules ~allow_new_public_modules;
331+
~existing_virtual_modules ~allow_new_public_modules ~is_vendored;
303332
let all_modules =
304333
Module_trie.mapi modules ~f:(fun _path (_, m) ->
305334
let name = [ Module.Source.name m ] in
@@ -326,7 +355,7 @@ let eval ~modules:(all_modules : Module.Source.t Module_trie.t) ~stanza_loc
326355
Module_trie.set all_modules path module_
327356

328357
let eval ~modules:(all_modules : Module.Source.t Module_trie.t) ~stanza_loc
329-
~private_modules ~kind ~src_dir
358+
~private_modules ~kind ~src_dir ~is_vendored
330359
(settings : Stanza_common.Modules_settings.t) =
331360
let eval0 =
332361
eval0
@@ -335,6 +364,6 @@ let eval ~modules:(all_modules : Module.Source.t Module_trie.t) ~stanza_loc
335364
in
336365
let modules =
337366
eval ~modules:all_modules ~stanza_loc ~private_modules ~kind ~src_dir
338-
settings eval0
367+
~is_vendored settings eval0
339368
in
340369
(eval0.modules, modules)

src/dune_rules/modules_field_evaluator.mli

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ val eval :
2525
-> private_modules:Ordered_set_lang.t
2626
-> kind:kind
2727
-> src_dir:Path.Build.t
28+
-> is_vendored:bool
2829
-> Stanza_common.Modules_settings.t
2930
-> (Loc.t * Module.Source.t) Module_trie.t * Module.t Module_trie.t

test/blackbox-tests/test-cases/intf-only/excluded-by-modules-field.t

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,53 @@
11
Specifying a module without implementation that isn't inside the (modules ..)
22
field
33

4-
$ cat >dune-project <<EOF
4+
$ cat > dune-project << EOF
55
> (lang dune 3.7)
66
> EOF
7-
$ cat >dune <<EOF
7+
8+
$ mkdir src
9+
$ cat > src/dune << EOF
810
> (library
911
> (name foo)
1012
> (wrapped false)
1113
> (modules_without_implementation x)
1214
> (modules y))
1315
> EOF
1416

15-
$ touch x.mli
17+
$ touch src/x.mli
1618

17-
$ cat >y.ml <<EOF
19+
$ cat > src/y.ml << EOF
1820
> module type F = X
1921
> EOF
2022

23+
X is warned about:
24+
2125
$ dune build --display short
22-
ocamlc .foo.objs/byte/y.{cmi,cmo,cmt} (exit 2)
23-
File "y.ml", line 1, characters 16-17:
26+
File "src/dune", line 4, characters 33-34:
27+
4 | (modules_without_implementation x)
28+
^
29+
Warning: These modules appear in the modules_without_implementation field:
30+
- X
31+
They must also appear in the modules field.
32+
ocamlc src/.foo.objs/byte/y.{cmi,cmo,cmt} (exit 2)
33+
File "src/y.ml", line 1, characters 16-17:
34+
1 | module type F = X
35+
^
36+
Error: Unbound module type X
37+
[1]
38+
39+
This should be ignored if we are in vendored_dirs
40+
41+
$ cat > dune << EOF
42+
> (vendored_dirs src)
43+
> (executable
44+
> (name bar)
45+
> (libraries foo))
46+
> EOF
47+
$ cat > bar.ml
48+
49+
$ dune build ./bar.exe
50+
File "src/y.ml", line 1, characters 16-17:
2451
1 | module type F = X
2552
^
2653
Error: Unbound module type X

test/blackbox-tests/test-cases/private-modules/private-module-excluded-by-modules-field.t

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,54 @@
11
Demonstrate the behavior when a module is listed by private_modules by not by
22
modules:
33

4-
$ cat >dune-project <<EOF
4+
$ cat > dune-project << EOF
55
> (lang dune 3.7)
66
> EOF
77

8-
$ cat >dune <<EOF
8+
$ mkdir src
9+
$ cat > src/dune << EOF
910
> (library
1011
> (name foo)
1112
> (wrapped false)
1213
> (modules y)
1314
> (private_modules x))
1415
> EOF
1516

16-
$ cat >x.ml <<EOF
17+
$ cat > src/x.ml << EOF
1718
> let foo = ()
1819
> EOF
1920

20-
$ cat >y.ml <<EOF
21+
$ cat > src/y.ml << EOF
2122
> let () = X.foo ()
2223
> EOF
2324

24-
X is silently ignored:
25+
X is warned about:
2526

2627
$ dune build
27-
File "y.ml", line 1, characters 9-14:
28+
File "src/dune", line 5, characters 18-19:
29+
5 | (private_modules x))
30+
^
31+
Warning: These modules appear in the private_modules field:
32+
- X
33+
They must also appear in the modules field.
34+
File "src/y.ml", line 1, characters 9-14:
35+
1 | let () = X.foo ()
36+
^^^^^
37+
Error: Unbound module X
38+
[1]
39+
40+
This warning should be ignored if we are in vendored_dirs
41+
42+
$ cat > dune << EOF
43+
> (vendored_dirs src)
44+
> (executable
45+
> (name bar)
46+
> (libraries foo))
47+
> EOF
48+
$ cat > bar.ml
49+
50+
$ dune build ./bar.exe
51+
File "src/y.ml", line 1, characters 9-14:
2852
1 | let () = X.foo ()
2953
^^^^^
3054
Error: Unbound module X

0 commit comments

Comments
 (0)