Skip to content

Commit a4c4d03

Browse files
authored
flambda-backend: Fix ghost locations for modular extensions (#1348)
* Fix ghost locations in modular extension AST nodes * Add missing ghostification Thank you, Carl! * Comment update about ghostiness (+ word-wrapping) * Add ghostify function (#1) * Add `Location.ghostify` * Update the parser's `make_ghost` to save an allocation in some cases * Promote parser.ml * Mark the inner mutable arrays for iarrays as ghost * Add comment about ghostification for comprehensions * Explain that ppxlib is where the ghostiness requirement is enforced * Use `Ast_helper.default_loc` to default the generated locations * Restore propagating the location, now via `Ast_helper.default_loc` * Drop obsolete comment * Rewrite advisory comment about locations
1 parent ca5a008 commit a4c4d03

File tree

12 files changed

+6393
-6364
lines changed

12 files changed

+6393
-6364
lines changed

boot/menhir/parser.ml

Lines changed: 6257 additions & 6254 deletions
Large diffs are not rendered by default.

parsing/extensions.ml

Lines changed: 43 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ open Extensions_parsing
1717
expression to translate. So we just check for the immutable arrays extension
1818
when processing a comprehension expression for an immutable array.
1919
20-
Note [Wrapping with make_extension]
21-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
20+
Note [Wrapping with make_entire_extension]
21+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2222
2323
The topmost node in the encoded AST must always look like e.g.
2424
[%extension.comprehensions]. This allows the decoding machinery to know
@@ -31,12 +31,12 @@ open Extensions_parsing
3131
structurally impossible/hard to forget taking this final step.
3232
3333
However, the final step is only one line of code (a call to
34-
[make_extension]), but yet the name of the extension varies, as does the type
35-
of the payload. It would thus take several lines of code to execute this
36-
command otherwise, along with dozens of lines to create the structure in the
37-
first place. And so instead we just manually call [make_extension] and refer
38-
to this Note as a reminder to authors of future extensions to remember to do
39-
this wrapping.
34+
[make_entire_extension]), but yet the name of the extension varies, as does
35+
the type of the payload. It would thus take several lines of code to execute
36+
this command otherwise, along with dozens of lines to create the structure in
37+
the first place. And so instead we just manually call [make_entire_extension]
38+
and refer to this Note as a reminder to authors of future extensions to
39+
remember to do this wrapping.
4040
*)
4141

4242
(** List and array comprehensions *)
@@ -89,77 +89,61 @@ module Comprehensions = struct
8989
v}
9090
*)
9191

92-
let comprehension_expr ~loc names x =
93-
Expression.wrap_desc ~loc ~attrs:[] @@ Expression.make_extension ~loc (extension_string :: names) x
92+
let comprehension_expr names x =
93+
Expression.wrap_desc ~attrs:[] @@
94+
Expression.make_extension (extension_string :: names) x
9495

9596
(** First, we define how to go from the nice AST to the OCaml AST; this is
9697
the [expr_of_...] family of expressions, culminating in
9798
[expr_of_comprehension_expr]. *)
9899

99-
let expr_of_iterator ~loc = function
100+
let expr_of_iterator = function
100101
| Range { start; stop; direction } ->
101102
comprehension_expr
102-
~loc
103103
[ "for"
104104
; "range"
105105
; match direction with
106106
| Upto -> "upto"
107107
| Downto -> "downto" ]
108108
(Ast_helper.Exp.tuple [start; stop])
109109
| In seq ->
110-
comprehension_expr ~loc ["for"; "in"] seq
110+
comprehension_expr ["for"; "in"] seq
111111

112-
let expr_of_clause_binding ~loc { pattern; iterator; attributes } =
113-
Ast_helper.Vb.mk
114-
~loc
115-
~attrs:attributes
116-
pattern
117-
(expr_of_iterator ~loc iterator)
112+
let expr_of_clause_binding { pattern; iterator; attributes } =
113+
Ast_helper.Vb.mk ~attrs:attributes pattern (expr_of_iterator iterator)
118114

119-
let expr_of_clause ~loc clause rest = match clause with
115+
let expr_of_clause clause rest = match clause with
120116
| For iterators ->
121117
comprehension_expr
122-
~loc
123118
["for"]
124119
(Ast_helper.Exp.let_
125-
Nonrecursive
126-
(List.map (expr_of_clause_binding ~loc) iterators)
120+
Nonrecursive (List.map expr_of_clause_binding iterators)
127121
rest)
128122
| When cond ->
129-
comprehension_expr
130-
~loc
131-
["when"]
132-
(Ast_helper.Exp.sequence cond rest)
123+
comprehension_expr ["when"] (Ast_helper.Exp.sequence cond rest)
133124

134-
let expr_of_comprehension ~loc ~type_ { body; clauses } =
125+
let expr_of_comprehension ~type_ { body; clauses } =
135126
comprehension_expr
136-
~loc
137127
type_
138128
(List.fold_right
139-
(expr_of_clause ~loc)
129+
expr_of_clause
140130
clauses
141-
(comprehension_expr ~loc ["body"] body))
131+
(comprehension_expr ["body"] body))
142132

143133
let expr_of ~loc eexpr =
144-
let ghost_loc = { loc with Location.loc_ghost = true } in
145-
let expr_of_comprehension_type type_ =
146-
expr_of_comprehension ~loc:ghost_loc ~type_
147-
in
148-
(* See Note [Wrapping with make_extension] *)
149-
Expression.make_extension ~loc [extension_string] @@
150-
match eexpr with
151-
| Cexp_list_comprehension comp ->
152-
expr_of_comprehension_type ["list"] comp
153-
| Cexp_array_comprehension (amut, comp) ->
154-
expr_of_comprehension_type
155-
[ "array"
156-
; match amut with
157-
| Mutable ->
158-
"mutable"
159-
| Immutable ->
160-
"immutable"
161-
]
162-
comp
134+
(* See Note [Wrapping with make_entire_extension] *)
135+
Expression.make_entire_extension ~loc extension_string (fun () ->
136+
match eexpr with
137+
| Cexp_list_comprehension comp ->
138+
expr_of_comprehension ~type_:["list"] comp
139+
| Cexp_array_comprehension (amut, comp) ->
140+
expr_of_comprehension
141+
~type_:[ "array"
142+
; match amut with
143+
| Mutable -> "mutable"
144+
| Immutable -> "immutable"
145+
]
146+
comp)
163147

164148
(** Then, we define how to go from the OCaml AST to the nice AST; this is
165149
the [..._of_expr] family of expressions, culminating in
@@ -280,19 +264,19 @@ module Immutable_arrays = struct
280264

281265
let expr_of ~loc = function
282266
| Iaexp_immutable_array elts ->
283-
(* See Note [Wrapping with make_extension] *)
284-
Expression.make_extension ~loc [extension_string] @@
285-
Ast_helper.Exp.array ~loc elts
267+
(* See Note [Wrapping with make_entire_extension] *)
268+
Expression.make_entire_extension ~loc extension_string (fun () ->
269+
Ast_helper.Exp.array elts)
286270

287271
let of_expr expr = match expr.pexp_desc with
288272
| Pexp_array elts -> Iaexp_immutable_array elts
289273
| _ -> failwith "Malformed immutable array expression"
290274

291275
let pat_of ~loc = function
292276
| Iapat_immutable_array elts ->
293-
(* See Note [Wrapping with make_extension] *)
294-
Pattern.make_extension ~loc [extension_string] @@
295-
Ast_helper.Pat.array ~loc elts
277+
(* See Note [Wrapping with make_entire_extension] *)
278+
Pattern.make_entire_extension ~loc extension_string (fun () ->
279+
Ast_helper.Pat.array elts)
296280

297281
let of_pat expr = match expr.ppat_desc with
298282
| Ppat_array elts -> Iapat_immutable_array elts
@@ -311,10 +295,10 @@ module Strengthen = struct
311295
[(module M)] can be the inferred type for [M], so this should be fine. *)
312296

313297
let mty_of ~loc { mty; mod_id } =
314-
(* See Note [Wrapping with make_extension] *)
315-
Module_type.make_extension ~loc [extension_string] @@
298+
(* See Note [Wrapping with make_entire_extension] *)
299+
Module_type.make_entire_extension ~loc extension_string (fun () ->
316300
Ast_helper.Mty.functor_ (Named (Location.mknoloc None, mty))
317-
(Ast_helper.Mty.alias mod_id)
301+
(Ast_helper.Mty.alias mod_id))
318302

319303
let of_mty mty = match mty.pmty_desc with
320304
| Pmty_functor(Named(_, mty), {pmty_desc = Pmty_alias mod_id}) ->

parsing/extensions_parsing.ml

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,17 @@ module type AST_parameters = sig
136136
[fun tm -> tm.pCAT_loc] for the appropriate syntactic category [CAT]. *)
137137
val location : ast -> Location.t
138138

139-
(** Turn an [ast_desc] into an [ast] by adding the appropriate metadata *)
139+
(** Turn an [ast_desc] into an [ast] by adding the appropriate metadata. When
140+
creating [ast] nodes afresh for an extension, the location should be
141+
omitted; in this case, it will default to [!Ast_helper.default_loc], which
142+
should be [ghost]. *)
140143
val wrap_desc :
141-
loc:Location.t -> attrs:Parsetree.attributes -> ast_desc -> ast
144+
?loc:Location.t -> attrs:Parsetree.attributes -> ast_desc -> ast
142145

143146
(** How to construct an extension node for this AST (something of the shape
144147
[[%name]] or [[%%name]], depending on the AST). Should just be
145-
[Ast_helper.CAT.extension] for the appropriate syntactic category
146-
[CAT]. *)
148+
[Ast_helper.CAT.extension] for the appropriate syntactic category [CAT].
149+
(This means that [?loc] should default to [!Ast_helper.default_loc.].) *)
147150
val make_extension_node :
148151
?loc:Location.t -> ?attrs:attributes -> extension -> ast
149152

@@ -172,9 +175,12 @@ module type AST = sig
172175
val location : ast -> Location.t
173176

174177
val wrap_desc :
175-
loc:Location.t -> attrs:Parsetree.attributes -> ast_desc -> ast
178+
?loc:Location.t -> attrs:Parsetree.attributes -> ast_desc -> ast
176179

177-
val make_extension : loc:Location.t -> string list -> ast -> ast_desc
180+
val make_extension : string list -> ast -> ast_desc
181+
182+
val make_entire_extension :
183+
loc:Location.t -> string -> (unit -> ast) -> ast_desc
178184

179185
val match_extension : ast -> (string list * ast) option
180186
end
@@ -195,12 +201,17 @@ module Make_AST (AST_parameters : AST_parameters) :
195201
struct
196202
include AST_parameters
197203

198-
let make_extension ~loc names =
204+
let make_extension names =
199205
make_extension_use
200206
~extension_node:
201207
(make_extension_node
202-
~loc
203-
({ txt = String.concat "." ("extension" :: names); loc }, PStr []))
208+
({ txt = String.concat "." ("extension" :: names);
209+
loc = !Ast_helper.default_loc },
210+
PStr []))
211+
212+
let make_entire_extension ~loc name ast =
213+
make_extension [name]
214+
(Ast_helper.with_default_loc (Location.ghostify loc) ast)
204215

205216
(* This raises an error if the language extension node is malformed.
206217
Malformed means either:
@@ -237,7 +248,7 @@ module Expression = Make_AST(struct
237248

238249
let location expr = expr.pexp_loc
239250

240-
let wrap_desc ~loc ~attrs = Ast_helper.Exp.mk ~loc ~attrs
251+
let wrap_desc ?loc ~attrs = Ast_helper.Exp.mk ?loc ~attrs
241252

242253
let make_extension_node = Ast_helper.Exp.extension
243254

@@ -262,7 +273,7 @@ module Pattern = Make_AST(struct
262273

263274
let location pat = pat.ppat_loc
264275

265-
let wrap_desc ~loc ~attrs = Ast_helper.Pat.mk ~loc ~attrs
276+
let wrap_desc ?loc ~attrs = Ast_helper.Pat.mk ?loc ~attrs
266277

267278
let make_extension_node = Ast_helper.Pat.extension
268279

@@ -286,7 +297,7 @@ module Module_type = Make_AST(struct
286297

287298
let location mty = mty.pmty_loc
288299

289-
let wrap_desc ~loc ~attrs = Ast_helper.Mty.mk ~loc ~attrs
300+
let wrap_desc ?loc ~attrs = Ast_helper.Mty.mk ?loc ~attrs
290301

291302
let make_extension_node = Ast_helper.Mty.extension
292303

parsing/extensions_parsing.mli

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,20 @@
3636
3737
b. We define an *overall auxiliary AST* for each syntactic category that's
3838
just for our language extensions; for expressions, it's called
39-
[Extensions.Expression.t]. It contains one constructor for each of the AST types
40-
defined as described in design point (1). This addresses concern (2); we
41-
can now match on actual OCaml constructors, as long as we can get a hold
42-
of them. And to do that…
43-
44-
c. We define a general scheme for how we represent language extensions in terms
45-
of the existing ASTs, and provide a few primitives for consuming/creating
46-
AST nodes of this form, for each syntactic category. There's not a lot
47-
of abstraction to be done, or at least it's not (yet) apparent what
48-
abstraction there is to do, so most of this remains manual. (Setting up
49-
a full lens-based/otherwise bidirectional approach sounds like a great
50-
opportunity for yak-shaving, but not *actually* a good idea.) This
51-
solves concern (3), and by doing it uniformly helps us address multiple
52-
cases at one stroke.
39+
[Extensions.Expression.t]. It contains one constructor for each of the
40+
AST types defined as described in design point (1). This addresses
41+
concern (2); we can now match on actual OCaml constructors, as long as we
42+
can get a hold of them. And to do that…
43+
44+
c. We define a general scheme for how we represent language extensions in
45+
terms of the existing ASTs, and provide a few primitives for
46+
consuming/creating AST nodes of this form, for each syntactic category.
47+
There's not a lot of abstraction to be done, or at least it's not (yet)
48+
apparent what abstraction there is to do, so most of this remains manual.
49+
(Setting up a full lens-based/otherwise bidirectional approach sounds
50+
like a great opportunity for yak-shaving, but not *actually* a good
51+
idea.) This solves concern (3), and by doing it uniformly helps us
52+
address multiple cases at one stroke.
5353
5454
Then, for each syntactic category, we define a module (in extensions.ml)
5555
that contains functions for converting between the Parsetree representation
@@ -68,7 +68,17 @@
6868
writing out extension points or attributes directly, we write the result of
6969
[Some_ast.make_extension ~loc [name1; name2; ...; NameN] a] as the special
7070
syntax [{% 'name1.name2.....nameN' | a %}] in the BNF. Other pieces of the
71-
OCaml AST are used as normal. *)
71+
OCaml AST are used as normal.
72+
73+
One detail which we hide as much as possible is locations: whenever
74+
constructing an OCaml AST node -- whether with [wrap_desc], the functions in
75+
[Ast_helper], or some other way -- the location should be left to be
76+
defaulted (and the default, [!Ast_helper.make_default], should be ghost).
77+
The [make_entire_extension] function will handle making sure this default
78+
location is set appropriately. If this isn't done and any locations on
79+
subterms aren't marked as ghost, the compiler will work fine, but ppxlib may
80+
detect that you've violated its well-formedness constraints and fail to
81+
parse the resulting AST. *)
7282

7383
(** Errors around the extension representation. These should mostly just be
7484
fatal, but they're needed for one test case
@@ -109,14 +119,28 @@ module type AST = sig
109119
(** How to get the location attached to an AST node *)
110120
val location : ast -> Location.t
111121

112-
(** Turn an [ast_desc] into an [ast] by adding the appropriate metadata *)
122+
(** Turn an [ast_desc] into an [ast] by adding the appropriate metadata. When
123+
creating [ast] nodes afresh for an extension, the location should be
124+
omitted; in this case, it will default to [!Ast_helper.default_loc], which
125+
should be [ghost]. *)
113126
val wrap_desc :
114-
loc:Location.t -> attrs:Parsetree.attributes -> ast_desc -> ast
115-
116-
(** Embed a language extension term in the AST with the given name
117-
and body (the [ast]). The name will be joined with dots
118-
and preceded by [extension.]. Partial inverse of [match_extension]. *)
119-
val make_extension : loc:Location.t -> string list -> ast -> ast_desc
127+
?loc:Location.t -> attrs:Parsetree.attributes -> ast_desc -> ast
128+
129+
(** Embed a language extension term in the AST with the given name and body
130+
(the [ast]). The name will be joined with dots and preceded by
131+
[extension.]. Any locations in the generated AST will be set to
132+
[!Ast_helper.default_loc], which should be [ghost]. Partial inverse of
133+
[match_extension]. *)
134+
val make_extension : string list -> ast -> ast_desc
135+
136+
(** As [make_extension], but specifically for the AST node corresponding to
137+
the entire piece of extension syntax (e.g., for a list comprehension, the
138+
whole [[x for x in xs]], and not a subterm like [for x in xs]). This sets
139+
[Ast_helper.default_loc] locally to the [ghost] version of the provided
140+
location, which is why the [ast] is generated from a function call; it is
141+
during this call that the location is so set. *)
142+
val make_entire_extension :
143+
loc:Location.t -> string -> (unit -> ast) -> ast_desc
120144

121145
(** Given an AST node, check if it's a language extension term; if it is,
122146
split it back up into its name (the [string list]) and the body (the

parsing/location.ml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ let init lexbuf fname =
4141
}
4242
;;
4343

44+
let ghostify l =
45+
if l.loc_ghost
46+
then l
47+
else { l with loc_ghost = true }
48+
4449
let symbol_rloc () = {
4550
loc_start = Parsing.symbol_start_pos ();
4651
loc_end = Parsing.symbol_end_pos ();

parsing/location.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ val init : Lexing.lexbuf -> string -> unit
6868
val curr : Lexing.lexbuf -> t
6969
(** Get the location of the current token from the [lexbuf]. *)
7070

71+
val ghostify : t -> t
72+
(** Return a version of the location with [loc_ghost = true] *)
73+
7174
val symbol_rloc: unit -> t
7275
val symbol_gloc: unit -> t
7376

parsing/parser.mly

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,10 @@ let lapply ~loc p1 p2 =
504504
let loc_map (f : 'a -> 'b) (x : 'a Location.loc) : 'b Location.loc =
505505
{ x with txt = f x.txt }
506506

507-
let make_ghost x = { x with loc = { x.loc with loc_ghost = true }}
507+
let make_ghost x =
508+
if x.loc.loc_ghost
509+
then x (* Save an allocation *)
510+
else { x with loc = Location.ghostify x.loc }
508511

509512
let loc_last (id : Longident.t Location.loc) : string Location.loc =
510513
loc_map Longident.last id

typing/parmatch.ml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,8 +1916,7 @@ module Conv = struct
19161916
| Mutable -> Ppat_array pats
19171917
| Immutable ->
19181918
Extensions.Immutable_arrays.pat_of
1919-
~loc:pat.pat_loc
1920-
(Iapat_immutable_array pats)
1919+
~loc:pat.pat_loc (Iapat_immutable_array pats)
19211920
in
19221921
mkpat ppat
19231922
| Tpat_lazy p ->

0 commit comments

Comments
 (0)