Skip to content

Commit 0609cea

Browse files
committed
Add -dune-optional-output mode for dune's internal use
Fixes ocaml-ppx#461 This PR adds a new command line flag that tells the driver not to write to the output file if there is no rewriting to be done. It's not 100% accurate if there are non context free transformations registered as we do not compare the AST for this feature but simply keep track of generated code via a hook. If any non context free transformation is registered, we simply assume it will rewrite something and always output. Signed-off-by: Nathan Rebours <[email protected]>
1 parent 455f217 commit 0609cea

File tree

7 files changed

+166
-26
lines changed

7 files changed

+166
-26
lines changed

Diff for: CHANGES.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
unreleased
22
----------
33

4+
- Add `-dune-optional-output` mode for dune's internal use
5+
(#482, @NathanReb)
46
- Insert errors from caught located exceptions in place of the code that
57
should have been generated by context-free rules. (#472, @NathanReb)
68

Diff for: src/driver.ml

+64-26
Original file line numberDiff line numberDiff line change
@@ -304,11 +304,13 @@ module Transform = struct
304304
in
305305
{ t with impl = Some map_impl; intf = Some map_intf }
306306

307+
let builtin_context_free_name = "<builtin:context-free>"
308+
307309
let builtin_of_context_free_rewriters ~hook ~rules ~enclose_impl ~enclose_intf
308310
~input_name =
309311
merge_into_generic_mappers ~hook ~input_name
310312
{
311-
name = "<builtin:context-free>";
313+
name = builtin_context_free_name;
312314
aliases = [];
313315
impl = None;
314316
intf = None;
@@ -323,6 +325,21 @@ module Transform = struct
323325
registered_at = Caller_id.get ~skip:[];
324326
}
325327

328+
(* Meant to be used after partitioning *)
329+
let rewrites_not_context_free t =
330+
match t with
331+
| { name; _ } when String.equal name builtin_context_free_name -> false
332+
| {
333+
impl = None;
334+
intf = None;
335+
instrument = None;
336+
preprocess_impl = None;
337+
preprocess_intf = None;
338+
_;
339+
} ->
340+
false
341+
| _ -> true
342+
326343
let partition_transformations ts =
327344
let before_instrs, after_instrs, rest =
328345
List.fold_left ts ~init:([], [], []) ~f:(fun (bef_i, aft_i, rest) t ->
@@ -528,11 +545,21 @@ let get_whole_ast_passes ~embed_errors ~hook ~expect_mismatch_handler ~tool_name
528545
linters @ preprocess @ before_instrs @ make_generic cts @ after_instrs
529546

530547
let apply_transforms ~tool_name ~file_path ~field ~lint_field ~dropped_so_far
531-
~hook ~expect_mismatch_handler ~input_name ~embed_errors ast =
548+
~hook ~expect_mismatch_handler ~input_name ~embed_errors ?rewritten ast =
549+
let _ = rewritten in
532550
let cts =
533551
get_whole_ast_passes ~tool_name ~embed_errors ~hook ~expect_mismatch_handler
534552
~input_name
535553
in
554+
(match rewritten with
555+
| None -> ()
556+
| Some rewritten -> (
557+
match List.filter cts ~f:Transform.rewrites_not_context_free with
558+
| [] -> ()
559+
| _ ->
560+
(* We won't be able to accurately tell whether any rewriting has
561+
happened *)
562+
rewritten := true));
536563
let finish (ast, _dropped, lint_errors, errors) =
537564
( ast,
538565
List.map lint_errors ~f:(fun (loc, s) ->
@@ -633,8 +660,8 @@ let sort_errors_by_loc errors =
633660

634661
(*$*)
635662

636-
let map_structure_gen st ~tool_name ~hook ~expect_mismatch_handler ~input_name
637-
~embed_errors =
663+
let map_structure_gen ~tool_name ~hook ~expect_mismatch_handler ~input_name
664+
~embed_errors ?rewritten st =
638665
Cookies.acknowledge_cookies T;
639666
if !perform_checks then (
640667
Attribute.reset_checks ();
@@ -693,7 +720,7 @@ let map_structure_gen st ~tool_name ~hook ~expect_mismatch_handler ~input_name
693720
~field:(fun (ct : Transform.t) -> ct.impl)
694721
~lint_field:(fun (ct : Transform.t) -> ct.lint_impl)
695722
~dropped_so_far:Attribute.dropped_so_far_structure ~hook
696-
~expect_mismatch_handler ~input_name ~embed_errors
723+
~expect_mismatch_handler ~input_name ~embed_errors ?rewritten
697724
in
698725
st |> lint lint_errors |> cookies_and_check |> with_errors (List.rev errors)
699726

@@ -703,14 +730,14 @@ let map_structure st =
703730
~tool_name:(Astlib.Ast_metadata.tool_name ())
704731
~hook:Context_free.Generated_code_hook.nop
705732
~expect_mismatch_handler:Context_free.Expect_mismatch_handler.nop
706-
~input_name:None ~embed_errors:false
733+
~input_name:None ~embed_errors:false ?rewritten:None
707734
with
708735
| ast -> ast
709736

710737
(*$ str_to_sig _last_text_block *)
711738

712-
let map_signature_gen sg ~tool_name ~hook ~expect_mismatch_handler ~input_name
713-
~embed_errors =
739+
let map_signature_gen ~tool_name ~hook ~expect_mismatch_handler ~input_name
740+
~embed_errors ?rewritten sg =
714741
Cookies.acknowledge_cookies T;
715742
if !perform_checks then (
716743
Attribute.reset_checks ();
@@ -769,7 +796,7 @@ let map_signature_gen sg ~tool_name ~hook ~expect_mismatch_handler ~input_name
769796
~field:(fun (ct : Transform.t) -> ct.intf)
770797
~lint_field:(fun (ct : Transform.t) -> ct.lint_intf)
771798
~dropped_so_far:Attribute.dropped_so_far_signature ~hook
772-
~expect_mismatch_handler ~input_name ~embed_errors
799+
~expect_mismatch_handler ~input_name ~embed_errors ?rewritten
773800
in
774801
sg |> lint lint_errors |> cookies_and_check |> with_errors (List.rev errors)
775802

@@ -779,7 +806,7 @@ let map_signature sg =
779806
~tool_name:(Astlib.Ast_metadata.tool_name ())
780807
~hook:Context_free.Generated_code_hook.nop
781808
~expect_mismatch_handler:Context_free.Expect_mismatch_handler.nop
782-
~input_name:None ~embed_errors:false
809+
~input_name:None ~embed_errors:false ?rewritten:None
783810
with
784811
| ast -> ast
785812

@@ -917,6 +944,7 @@ type output_mode =
917944
| Dparsetree
918945
| Reconcile of Reconcile.mode
919946
| Null
947+
| Dune_optional_output
920948

921949
(*$*)
922950
let extract_cookies_str st =
@@ -1036,14 +1064,14 @@ struct
10361064
let set x = t.data <- Some x
10371065
end
10381066

1039-
let process_ast (ast : Intf_or_impl.t) ~input_name ~tool_name ~hook
1040-
~expect_mismatch_handler ~embed_errors =
1067+
let process_ast ~input_name ~tool_name ~hook ~expect_mismatch_handler
1068+
~embed_errors ?rewritten (ast : Intf_or_impl.t) =
10411069
match ast with
10421070
| Intf x ->
10431071
let ast =
10441072
match
10451073
map_signature_gen x ~tool_name ~hook ~expect_mismatch_handler
1046-
~input_name:(Some input_name) ~embed_errors
1074+
~input_name:(Some input_name) ~embed_errors ?rewritten
10471075
with
10481076
| ast -> ast
10491077
in
@@ -1052,18 +1080,28 @@ let process_ast (ast : Intf_or_impl.t) ~input_name ~tool_name ~hook
10521080
let ast =
10531081
match
10541082
map_structure_gen x ~tool_name ~hook ~expect_mismatch_handler
1055-
~input_name:(Some input_name) ~embed_errors
1083+
~input_name:(Some input_name) ~embed_errors ?rewritten
10561084
with
10571085
| ast -> ast
10581086
in
10591087
Intf_or_impl.Impl ast
10601088

1089+
let pp_ast ~output (ast : Intf_or_impl.t) =
1090+
with_output output ~binary:false ~f:(fun oc ->
1091+
let ppf = Stdlib.Format.formatter_of_out_channel oc in
1092+
(match ast with
1093+
| Intf ast -> Pprintast.signature ppf ast
1094+
| Impl ast -> Pprintast.structure ppf ast);
1095+
let null_ast = match ast with Intf [] | Impl [] -> true | _ -> false in
1096+
if not null_ast then Stdlib.Format.pp_print_newline ppf ())
1097+
10611098
let process_file (kind : Kind.t) fn ~input_name ~relocate ~output_mode
10621099
~embed_errors ~output =
10631100
File_property.reset_all ();
10641101
List.iter (List.rev !process_file_hooks) ~f:(fun f -> f ());
10651102
corrections := [];
10661103
let replacements = ref [] in
1104+
let rewritten = ref false in
10671105
let tool_name = "ppx_driver" in
10681106
let hook : Context_free.Generated_code_hook.t =
10691107
match output_mode with
@@ -1075,6 +1113,7 @@ let process_file (kind : Kind.t) fn ~input_name ~relocate ~output_mode
10751113
(Reconcile.Replacement.make () ~context:(Extension context)
10761114
~start:loc.loc_start ~stop:loc.loc_end ~repl:generated));
10771115
}
1116+
| Dune_optional_output -> { f = (fun _ _ _ -> rewritten := true) }
10781117
| _ -> Context_free.Generated_code_hook.nop
10791118
in
10801119
let expect_mismatch_handler : Context_free.Expect_mismatch_handler.t =
@@ -1097,7 +1136,7 @@ let process_file (kind : Kind.t) fn ~input_name ~relocate ~output_mode
10971136
let ast =
10981137
extract_cookies ast
10991138
|> process_ast ~input_name ~tool_name ~hook ~expect_mismatch_handler
1100-
~embed_errors
1139+
~embed_errors ~rewritten
11011140
in
11021141
(input_fname, input_version, ast)
11031142
with exn when embed_errors ->
@@ -1134,16 +1173,8 @@ let process_file (kind : Kind.t) fn ~input_name ~relocate ~output_mode
11341173

11351174
(match output_mode with
11361175
| Null -> ()
1137-
| Pretty_print ->
1138-
with_output output ~binary:false ~f:(fun oc ->
1139-
let ppf = Stdlib.Format.formatter_of_out_channel oc in
1140-
(match ast with
1141-
| Intf ast -> Pprintast.signature ppf ast
1142-
| Impl ast -> Pprintast.structure ppf ast);
1143-
let null_ast =
1144-
match ast with Intf [] | Impl [] -> true | _ -> false
1145-
in
1146-
if not null_ast then Stdlib.Format.pp_print_newline ppf ())
1176+
| Pretty_print -> pp_ast ~output ast
1177+
| Dune_optional_output -> if !rewritten then pp_ast ~output ast
11471178
| Dump_ast ->
11481179
with_output output ~binary:true ~f:(fun oc ->
11491180
Ast_io.write oc
@@ -1191,7 +1222,10 @@ let set_output_mode mode =
11911222
match (!output_mode, mode) with
11921223
| Pretty_print, _ -> output_mode := mode
11931224
| _, Pretty_print -> assert false
1194-
| Dump_ast, Dump_ast | Dparsetree, Dparsetree -> ()
1225+
| Dune_optional_output, Dune_optional_output
1226+
| Dump_ast, Dump_ast
1227+
| Dparsetree, Dparsetree ->
1228+
()
11951229
| Reconcile a, Reconcile b when Poly.equal a b -> ()
11961230
| x, y ->
11971231
let arg_of_output_mode = function
@@ -1201,6 +1235,7 @@ let set_output_mode mode =
12011235
| Reconcile Using_line_directives -> "-reconcile"
12021236
| Reconcile Delimiting_generated_blocks -> "-reconcile-with-comments"
12031237
| Null -> "-null"
1238+
| Dune_optional_output -> "-dune-optional-output"
12041239
in
12051240
raise
12061241
(Arg.Bad
@@ -1409,6 +1444,9 @@ let standalone_args =
14091444
( "-corrected-suffix",
14101445
Arg.Set_string corrected_suffix,
14111446
"SUFFIX Suffix to append to corrected files" );
1447+
( "-dune-optional-output",
1448+
Arg.Unit (fun () -> set_output_mode Dune_optional_output),
1449+
" For dune's internal use only" );
14121450
]
14131451

14141452
let get_args ?(standalone_args = standalone_args) () =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
open Ppxlib
2+
3+
let rule =
4+
Context_free.Rule.extension
5+
(Extension.V3.declare "iam1" Extension.Context.expression
6+
Ast_pattern.(pstr nil)
7+
(fun ~ctxt ->
8+
let loc = Expansion_context.Extension.extension_point_loc ctxt in
9+
[%expr 1]))
10+
11+
let () = Driver.register_transformation ~rules:[ rule ] "iam1"
12+
let () = Driver.standalone ()

Diff for: test/driver/dune-optional-output/driver_with_impl.ml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
open Ppxlib
2+
3+
let rule =
4+
Context_free.Rule.extension
5+
(Extension.V3.declare "iam1" Extension.Context.expression
6+
Ast_pattern.(pstr nil)
7+
(fun ~ctxt ->
8+
let loc = Expansion_context.Extension.extension_point_loc ctxt in
9+
[%expr 1]))
10+
11+
let () = Driver.register_transformation ~rules:[ rule ] "iam1"
12+
13+
let () =
14+
Driver.register_transformation ~impl:(fun str -> str) "IdentityInDisguise"
15+
16+
let () = Driver.standalone ()

Diff for: test/driver/dune-optional-output/dune

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
(executable
2+
(name context_free_only_driver)
3+
(libraries ppxlib)
4+
(preprocess
5+
(pps ppxlib.metaquot))
6+
(modules context_free_only_driver))
7+
8+
(executable
9+
(name driver_with_impl)
10+
(libraries ppxlib)
11+
(preprocess
12+
(pps ppxlib.metaquot))
13+
(modules driver_with_impl))
14+
15+
(cram
16+
(deps context_free_only_driver.exe driver_with_impl.exe))

Diff for: test/driver/dune-optional-output/run.t

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
The -dune-optional-output flag is meant for dune to be able
2+
to use ppx internally without having a build dependency on ppxlib
3+
or any ppx.
4+
5+
When enabled, it should not write to the output if it can absolutely
6+
tell no transformation occured.
7+
8+
We have a driver with a single context free rule to expand [%iam1] extension
9+
10+
Let us consider the following file:
11+
12+
$ cat > foo.ml << EOF
13+
> let x = 1
14+
> let y = 2
15+
> EOF
16+
17+
If we call the driver with the -dune-optional-output flag, it should not write a file:
18+
19+
$ ./context_free_only_driver.exe -impl -dune-optional-output -o foo.pp.ml foo.ml
20+
$ ls foo.*
21+
foo.ml
22+
23+
We can see that it did not write test.pp.ml
24+
25+
Now if we actually use the extension:
26+
27+
$ cat > bar.ml << EOF
28+
> let x = [%iam1]
29+
> let y = 2
30+
> EOF
31+
32+
It should actually detect the transformation and therefore write the output file:
33+
34+
$ ./context_free_only_driver.exe -impl -dune-optional-output -o bar.pp.ml bar.ml
35+
$ ls bar.*
36+
bar.ml
37+
bar.pp.ml
38+
39+
Now we have another driver that has the same context free rule but also another
40+
transformation with an "impl", i.e. a rule to rewrite the whole AST unconditionnally.
41+
This rule does not rewrite anything and is just the identity rewriter.
42+
We cannot tell without actually comparing the ASTs if any rewriting happened so in
43+
that case we always write to the output.
44+
45+
$ cat > baz.ml << EOF
46+
> let x = 1
47+
> let y = 2
48+
> EOF
49+
$ ./driver_with_impl.exe -impl -dune-optional-output -o baz.pp.ml baz.ml
50+
$ ls baz.*
51+
baz.ml
52+
baz.pp.ml

Diff for: test/driver/run_as_ppx_rewriter_preserve_version/dune

+4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
(executable
22
(name identity_standalone)
33
(libraries ppxlib)
4+
(preprocess
5+
(pps ppxlib.metaquot))
46
(modules identity_standalone))
57

68
(executable
79
(name print_magic_number)
810
(libraries astlib)
11+
(preprocess
12+
(pps ppxlib.metaquot))
913
(modules print_magic_number))
1014

1115
(cram

0 commit comments

Comments
 (0)