Skip to content

Commit 524c67a

Browse files
committed
Add -dune-optional-output mode for dune's internal use
Fixes #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 524c67a

File tree

7 files changed

+165
-26
lines changed

7 files changed

+165
-26
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
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

src/driver.ml

Lines changed: 63 additions & 26 deletions
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,20 @@ 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 =
532549
let cts =
533550
get_whole_ast_passes ~tool_name ~embed_errors ~hook ~expect_mismatch_handler
534551
~input_name
535552
in
553+
(match rewritten with
554+
| None -> ()
555+
| Some rewritten -> (
556+
match List.filter cts ~f:Transform.rewrites_not_context_free with
557+
| [] -> ()
558+
| _ ->
559+
(* We won't be able to accurately tell whether any rewriting has
560+
happened *)
561+
rewritten := true));
536562
let finish (ast, _dropped, lint_errors, errors) =
537563
( ast,
538564
List.map lint_errors ~f:(fun (loc, s) ->
@@ -633,8 +659,8 @@ let sort_errors_by_loc errors =
633659

634660
(*$*)
635661

636-
let map_structure_gen st ~tool_name ~hook ~expect_mismatch_handler ~input_name
637-
~embed_errors =
662+
let map_structure_gen ~tool_name ~hook ~expect_mismatch_handler ~input_name
663+
~embed_errors ?rewritten st =
638664
Cookies.acknowledge_cookies T;
639665
if !perform_checks then (
640666
Attribute.reset_checks ();
@@ -693,7 +719,7 @@ let map_structure_gen st ~tool_name ~hook ~expect_mismatch_handler ~input_name
693719
~field:(fun (ct : Transform.t) -> ct.impl)
694720
~lint_field:(fun (ct : Transform.t) -> ct.lint_impl)
695721
~dropped_so_far:Attribute.dropped_so_far_structure ~hook
696-
~expect_mismatch_handler ~input_name ~embed_errors
722+
~expect_mismatch_handler ~input_name ~embed_errors ?rewritten
697723
in
698724
st |> lint lint_errors |> cookies_and_check |> with_errors (List.rev errors)
699725

@@ -703,14 +729,14 @@ let map_structure st =
703729
~tool_name:(Astlib.Ast_metadata.tool_name ())
704730
~hook:Context_free.Generated_code_hook.nop
705731
~expect_mismatch_handler:Context_free.Expect_mismatch_handler.nop
706-
~input_name:None ~embed_errors:false
732+
~input_name:None ~embed_errors:false ?rewritten:None
707733
with
708734
| ast -> ast
709735

710736
(*$ str_to_sig _last_text_block *)
711737

712-
let map_signature_gen sg ~tool_name ~hook ~expect_mismatch_handler ~input_name
713-
~embed_errors =
738+
let map_signature_gen ~tool_name ~hook ~expect_mismatch_handler ~input_name
739+
~embed_errors ?rewritten sg =
714740
Cookies.acknowledge_cookies T;
715741
if !perform_checks then (
716742
Attribute.reset_checks ();
@@ -769,7 +795,7 @@ let map_signature_gen sg ~tool_name ~hook ~expect_mismatch_handler ~input_name
769795
~field:(fun (ct : Transform.t) -> ct.intf)
770796
~lint_field:(fun (ct : Transform.t) -> ct.lint_intf)
771797
~dropped_so_far:Attribute.dropped_so_far_signature ~hook
772-
~expect_mismatch_handler ~input_name ~embed_errors
798+
~expect_mismatch_handler ~input_name ~embed_errors ?rewritten
773799
in
774800
sg |> lint lint_errors |> cookies_and_check |> with_errors (List.rev errors)
775801

@@ -779,7 +805,7 @@ let map_signature sg =
779805
~tool_name:(Astlib.Ast_metadata.tool_name ())
780806
~hook:Context_free.Generated_code_hook.nop
781807
~expect_mismatch_handler:Context_free.Expect_mismatch_handler.nop
782-
~input_name:None ~embed_errors:false
808+
~input_name:None ~embed_errors:false ?rewritten:None
783809
with
784810
| ast -> ast
785811

@@ -917,6 +943,7 @@ type output_mode =
917943
| Dparsetree
918944
| Reconcile of Reconcile.mode
919945
| Null
946+
| Dune_optional_output
920947

921948
(*$*)
922949
let extract_cookies_str st =
@@ -1036,14 +1063,14 @@ struct
10361063
let set x = t.data <- Some x
10371064
end
10381065

1039-
let process_ast (ast : Intf_or_impl.t) ~input_name ~tool_name ~hook
1040-
~expect_mismatch_handler ~embed_errors =
1066+
let process_ast ~input_name ~tool_name ~hook ~expect_mismatch_handler
1067+
~embed_errors ?rewritten (ast : Intf_or_impl.t) =
10411068
match ast with
10421069
| Intf x ->
10431070
let ast =
10441071
match
10451072
map_signature_gen x ~tool_name ~hook ~expect_mismatch_handler
1046-
~input_name:(Some input_name) ~embed_errors
1073+
~input_name:(Some input_name) ~embed_errors ?rewritten
10471074
with
10481075
| ast -> ast
10491076
in
@@ -1052,18 +1079,28 @@ let process_ast (ast : Intf_or_impl.t) ~input_name ~tool_name ~hook
10521079
let ast =
10531080
match
10541081
map_structure_gen x ~tool_name ~hook ~expect_mismatch_handler
1055-
~input_name:(Some input_name) ~embed_errors
1082+
~input_name:(Some input_name) ~embed_errors ?rewritten
10561083
with
10571084
| ast -> ast
10581085
in
10591086
Intf_or_impl.Impl ast
10601087

1088+
let pp_ast ~output (ast : Intf_or_impl.t) =
1089+
with_output output ~binary:false ~f:(fun oc ->
1090+
let ppf = Stdlib.Format.formatter_of_out_channel oc in
1091+
(match ast with
1092+
| Intf ast -> Pprintast.signature ppf ast
1093+
| Impl ast -> Pprintast.structure ppf ast);
1094+
let null_ast = match ast with Intf [] | Impl [] -> true | _ -> false in
1095+
if not null_ast then Stdlib.Format.pp_print_newline ppf ())
1096+
10611097
let process_file (kind : Kind.t) fn ~input_name ~relocate ~output_mode
10621098
~embed_errors ~output =
10631099
File_property.reset_all ();
10641100
List.iter (List.rev !process_file_hooks) ~f:(fun f -> f ());
10651101
corrections := [];
10661102
let replacements = ref [] in
1103+
let rewritten = ref false in
10671104
let tool_name = "ppx_driver" in
10681105
let hook : Context_free.Generated_code_hook.t =
10691106
match output_mode with
@@ -1075,6 +1112,7 @@ let process_file (kind : Kind.t) fn ~input_name ~relocate ~output_mode
10751112
(Reconcile.Replacement.make () ~context:(Extension context)
10761113
~start:loc.loc_start ~stop:loc.loc_end ~repl:generated));
10771114
}
1115+
| Dune_optional_output -> { f = (fun _ _ _ -> rewritten := true) }
10781116
| _ -> Context_free.Generated_code_hook.nop
10791117
in
10801118
let expect_mismatch_handler : Context_free.Expect_mismatch_handler.t =
@@ -1097,7 +1135,7 @@ let process_file (kind : Kind.t) fn ~input_name ~relocate ~output_mode
10971135
let ast =
10981136
extract_cookies ast
10991137
|> process_ast ~input_name ~tool_name ~hook ~expect_mismatch_handler
1100-
~embed_errors
1138+
~embed_errors ~rewritten
11011139
in
11021140
(input_fname, input_version, ast)
11031141
with exn when embed_errors ->
@@ -1134,16 +1172,8 @@ let process_file (kind : Kind.t) fn ~input_name ~relocate ~output_mode
11341172

11351173
(match output_mode with
11361174
| 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 ())
1175+
| Pretty_print -> pp_ast ~output ast
1176+
| Dune_optional_output -> if !rewritten then pp_ast ~output ast
11471177
| Dump_ast ->
11481178
with_output output ~binary:true ~f:(fun oc ->
11491179
Ast_io.write oc
@@ -1191,7 +1221,10 @@ let set_output_mode mode =
11911221
match (!output_mode, mode) with
11921222
| Pretty_print, _ -> output_mode := mode
11931223
| _, Pretty_print -> assert false
1194-
| Dump_ast, Dump_ast | Dparsetree, Dparsetree -> ()
1224+
| Dune_optional_output, Dune_optional_output
1225+
| Dump_ast, Dump_ast
1226+
| Dparsetree, Dparsetree ->
1227+
()
11951228
| Reconcile a, Reconcile b when Poly.equal a b -> ()
11961229
| x, y ->
11971230
let arg_of_output_mode = function
@@ -1201,6 +1234,7 @@ let set_output_mode mode =
12011234
| Reconcile Using_line_directives -> "-reconcile"
12021235
| Reconcile Delimiting_generated_blocks -> "-reconcile-with-comments"
12031236
| Null -> "-null"
1237+
| Dune_optional_output -> "-dune-optional-output"
12041238
in
12051239
raise
12061240
(Arg.Bad
@@ -1409,6 +1443,9 @@ let standalone_args =
14091443
( "-corrected-suffix",
14101444
Arg.Set_string corrected_suffix,
14111445
"SUFFIX Suffix to append to corrected files" );
1446+
( "-dune-optional-output",
1447+
Arg.Unit (fun () -> set_output_mode Dune_optional_output),
1448+
" For dune's internal use only" );
14121449
]
14131450

14141451
let get_args ?(standalone_args = standalone_args) () =
Lines changed: 12 additions & 0 deletions
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 ()
Lines changed: 16 additions & 0 deletions
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 ()
Lines changed: 16 additions & 0 deletions
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))
Lines changed: 52 additions & 0 deletions
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 foo.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 unconditionally.
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

test/driver/run_as_ppx_rewriter_preserve_version/dune

Lines changed: 4 additions & 0 deletions
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)