Skip to content

Commit 9d3b5a1

Browse files
authored
flambda-backend: Provide an AST-like view of modular extension extension node names (#1362)
That is: if you have `[%extension.comprehensions.for.in]`, this will be represented in OCaml as `Extensions_parsing.Extension_node_name.("comprehensions" :: ["for"; "in"])`; we're guaranteed to have it be nonempty, we can pass it around as a unit, and most importantly we can change the representation in a single place (e.g., to `[%jst.comprehensions.for.in]`, or to `[%extension'comprehensions'for'in]`, etc.). Some minor error changes come along as well, either around spacing (due to printing changes) or the locations in errors that the user will never see.
1 parent 7a92219 commit 9d3b5a1

File tree

6 files changed

+170
-79
lines changed

6 files changed

+170
-79
lines changed

parsing/extensions.ml

+14-13
Original file line numberDiff line numberDiff line change
@@ -151,26 +151,27 @@ module Comprehensions = struct
151151

152152
module Desugaring_error = struct
153153
type error =
154-
| Non_comprehension_extension_point of string list
154+
| Non_comprehension_extension_point of Extension_node_name.t
155155
| Non_extension
156156
| Bad_comprehension_extension_point of string list
157157
| No_clauses
158158

159159
let report_error ~loc = function
160-
| Non_comprehension_extension_point name ->
160+
| Non_comprehension_extension_point ext_name ->
161161
Location.errorf ~loc
162-
"Tried to desugar the non-comprehension extension point \
163-
\"extension.%s\" as part of a comprehension expression"
164-
(String.concat "." name)
162+
"Tried to desugar the non-comprehension extension point %a@ \
163+
as part of a comprehension expression"
164+
Extension_node_name.pp_quoted_name ext_name
165165
| Non_extension ->
166166
Location.errorf ~loc
167-
"Tried to desugar a non-extension expression as part of a \
168-
comprehension expression"
169-
| Bad_comprehension_extension_point name ->
167+
"Tried to desugar a non-extension expression@ \
168+
as part of a comprehension expression"
169+
| Bad_comprehension_extension_point subparts ->
170170
Location.errorf ~loc
171-
"Unknown, unexpected, or malformed comprehension extension point \
172-
\"extension.comprehension.%s\""
173-
(String.concat "." name)
171+
"Unknown, unexpected, or malformed@ \
172+
comprehension extension point %a"
173+
Extension_node_name.pp_quoted_name
174+
Extension_node_name.(extension_string :: subparts)
174175
| No_clauses ->
175176
Location.errorf ~loc
176177
"Tried to desugar a comprehension with no clauses"
@@ -191,8 +192,8 @@ module Comprehensions = struct
191192
| Some (comprehensions :: names, expr)
192193
when String.equal comprehensions extension_string ->
193194
names, expr
194-
| Some (name, _) ->
195-
Desugaring_error.raise expr (Non_comprehension_extension_point name)
195+
| Some (ext_name, _) ->
196+
Desugaring_error.raise expr (Non_comprehension_extension_point ext_name)
196197
| None ->
197198
Desugaring_error.raise expr Non_extension
198199

parsing/extensions_parsing.ml

+114-33
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,100 @@
4141

4242
open Parsetree
4343

44+
(******************************************************************************)
45+
(** Collect all the extension-node-name-building machinery in one place so that
46+
it can be changed all at once. *)
47+
48+
(** An AST-style representation of the names used when generating extension
49+
nodes for modular extensions; see the .mli file for more details. *)
50+
module Extension_node_name : sig
51+
(** A nonempty list of name components, without the leading root [extension.];
52+
see the .mli file for more details. *)
53+
type t = ( :: ) of string * string list
54+
55+
(** Convert a modular extension extension node's name to the string form used
56+
in the OCaml AST; not exposed. *)
57+
val to_string : t -> string
58+
59+
(** Parse an OCaml extension node's name:
60+
- [Some (Ok _)] if it's a legal modular extension name;
61+
- [Some (Error ())] if it's the bare [extension]; and
62+
- [None] if it doesn't start with the leading [extension].
63+
Not exposed. *)
64+
val of_string : string -> (t, unit) result option
65+
66+
(** Print out a modular extension extension node name, in quotes; for use in
67+
error messages. *)
68+
val pp_quoted_name : Format.formatter -> t -> unit
69+
70+
(** Print out an empty extension node with a modular extension name,
71+
accompanied by an indefinite article; for use in error messages. Not
72+
exposed. *)
73+
val pp_a_node : Format.formatter -> t -> unit
74+
75+
(** Print out the illegal empty quasi-modular extension extension node with no
76+
name beyond [extension]; for use in error messages. Not exposed. *)
77+
val pp_bad_empty_node : Format.formatter -> unit -> unit
78+
end = struct
79+
(** The three parameters that control how we encode modular extension
80+
extension node names. When updating these, update comments that refer to
81+
them! *)
82+
module Config = struct
83+
(** The separator between name components *)
84+
let separator = '.'
85+
86+
(** The leading namespace that identifies this extension point as reserved
87+
for a modular extension *)
88+
let root = "extension"
89+
90+
(** For printing purposes, the appropriate indefinite article for [root] *)
91+
let article = "an"
92+
end
93+
94+
include Config
95+
let separator_str = String.make 1 separator
96+
97+
type t = ( :: ) of string * string list
98+
99+
let to_string (ext :: subparts) =
100+
String.concat separator_str (root :: ext :: subparts)
101+
102+
let of_string str = match String.split_on_char separator str with
103+
| root' :: parts when String.equal root root' -> begin
104+
match parts with
105+
| ext :: subparts -> Some (Ok (ext :: subparts))
106+
| [] -> Some (Error ())
107+
end
108+
| _ :: _ | [] -> None
109+
110+
let pp_quoted_name ppf t = Format.fprintf ppf "\"%s\"" (to_string t)
111+
112+
let pp_extension_node ppf id = Format.fprintf ppf "[%%%s]" id
113+
114+
let pp_a_node ppf t =
115+
Format.fprintf ppf "%s %a" article pp_extension_node (to_string t)
116+
117+
let pp_bad_empty_node ppf () = pp_extension_node ppf root
118+
end
119+
44120
(******************************************************************************)
45121
module Error = struct
122+
(** Someone used [[%extension.EXTNAME]] wrong *)
46123
type malformed_extension =
47124
| Has_payload of payload
48125

126+
(** An error triggered when desugaring a language extension from an OCaml
127+
AST; should always be fatal *)
49128
type error =
50-
| Malformed_extension of string list * malformed_extension
129+
| Malformed_extension of Extension_node_name.t * malformed_extension
51130
| Unknown_extension of string
52131
| Disabled_extension of Language_extension.t
53132
| Wrong_syntactic_category of Language_extension.t * string
54133
| Unnamed_extension
55-
| Bad_introduction of string * string list
134+
| Bad_introduction of Extension_node_name.t
56135

136+
(** The exception type thrown when desugaring a language extension from an
137+
OCaml AST *)
57138
exception Error of Location.t * error
58139
end
59140

@@ -65,23 +146,21 @@ let assert_extension_enabled ~loc ext =
65146
;;
66147

67148
let report_error ~loc = function
68-
| Malformed_extension(name, malformed) -> begin
69-
let name = String.concat "." ("extension" :: name) in
149+
| Malformed_extension(ext_name, malformed) -> begin
70150
match malformed with
71151
| Has_payload _payload ->
72152
Location.errorf
73153
~loc
74154
"@[Modular extension nodes are not allowed to have a payload,@ \
75-
but \"%s\" does@]"
76-
name
155+
but %a does@]"
156+
Extension_node_name.pp_quoted_name ext_name
77157
end
78158
| Unknown_extension name ->
79159
Location.errorf
80160
~loc
81-
"@[Unknown extension \"%s\" referenced via an@ [%%extension.%s] \
82-
extension node@]"
83-
name
161+
"@[Unknown extension \"%s\" referenced via@ %a extension node@]"
84162
name
163+
Extension_node_name.pp_a_node Extension_node_name.[name]
85164
| Disabled_extension ext ->
86165
Location.errorf
87166
~loc
@@ -96,15 +175,16 @@ let report_error ~loc = function
96175
| Unnamed_extension ->
97176
Location.errorf
98177
~loc
99-
"Cannot have an extension node named [%%extension]"
100-
| Bad_introduction(name, subnames) ->
178+
"Cannot have an extension node named %a"
179+
Extension_node_name.pp_bad_empty_node ()
180+
| Bad_introduction(ext :: _ as ext_name) ->
101181
Location.errorf
102182
~loc
103-
"@[The extension \"%s\" was referenced improperly; it started with an@ \
104-
[%%extension.%s] extension node,@ not an [%%extension.%s] one@]"
105-
name
106-
(String.concat "." (name :: subnames))
107-
name
183+
"@[The extension \"%s\" was referenced improperly; it started with@ %a \
184+
extension node,@ not %a one@]"
185+
ext
186+
Extension_node_name.pp_a_node ext_name
187+
Extension_node_name.pp_a_node Extension_node_name.[ext]
108188

109189
let () =
110190
Location.register_error_of_exn
@@ -177,19 +257,19 @@ module type AST = sig
177257
val wrap_desc :
178258
?loc:Location.t -> attrs:Parsetree.attributes -> ast_desc -> ast
179259

180-
val make_extension : string list -> ast -> ast_desc
260+
val make_extension : Extension_node_name.t -> ast -> ast_desc
181261

182262
val make_entire_extension :
183263
loc:Location.t -> string -> (unit -> ast) -> ast_desc
184264

185-
val match_extension : ast -> (string list * ast) option
265+
val match_extension : ast -> (Extension_node_name.t * ast) option
186266
end
187267

188268
(* Some extensions written before this file existed are handled in their own
189269
way; this function filters them out. *)
190-
let uniformly_handled_extension names =
191-
match names with
192-
| [("local"|"global"|"nonlocal"|"escape"|"include_functor"|"curry")] -> false
270+
let uniformly_handled_extension name =
271+
match name with
272+
| "local"|"global"|"nonlocal"|"escape"|"include_functor"|"curry" -> false
193273
| _ -> true
194274

195275
(** Given the [AST_parameters] for a syntactic category, produce the
@@ -201,12 +281,12 @@ module Make_AST (AST_parameters : AST_parameters) :
201281
struct
202282
include AST_parameters
203283

204-
let make_extension names =
284+
let make_extension ext_name =
205285
make_extension_use
206286
~extension_node:
207287
(make_extension_node
208-
({ txt = String.concat "." ("extension" :: names);
209-
loc = !Ast_helper.default_loc },
288+
({ txt = Extension_node_name.to_string ext_name
289+
; loc = !Ast_helper.default_loc },
210290
PStr []))
211291

212292
let make_entire_extension ~loc name ast =
@@ -225,16 +305,19 @@ module Make_AST (AST_parameters : AST_parameters) :
225305
match match_extension_use ast with
226306
| Some (({txt = ext_name; loc = ext_loc}, ext_payload), body) ->
227307
begin
228-
match String.split_on_char '.' ext_name with
229-
| "extension" :: names when uniformly_handled_extension names -> begin
308+
let raise_error err = raise (Error(ext_loc, err)) in
309+
match Extension_node_name.of_string ext_name with
310+
| Some (Ok (ext :: _ as ext_name))
311+
when uniformly_handled_extension ext -> begin
230312
let raise_malformed err =
231-
raise (Error(ext_loc, Malformed_extension(names, err)))
313+
raise_error (Malformed_extension(ext_name, err))
232314
in
233315
match ext_payload with
234-
| PStr [] -> Some (names, body)
316+
| PStr [] -> Some (ext_name, body)
235317
| _ -> raise_malformed (Has_payload ext_payload)
236318
end
237-
| _ -> None
319+
| Some (Error ()) -> raise_error Unnamed_extension
320+
| Some (Ok (_ :: _)) | None -> None
238321
end
239322
| None -> None
240323
end
@@ -341,8 +424,6 @@ end = struct
341424
end
342425
| None -> raise_error (Unknown_extension name)
343426
end
344-
| Some ([], _) ->
345-
raise_error Unnamed_extension
346-
| Some (name :: subnames, _) ->
347-
raise_error (Bad_introduction(name, subnames))
427+
| Some (_ :: _ :: _ as ext_name, _) ->
428+
raise_error (Bad_introduction(ext_name))
348429
end

parsing/extensions_parsing.mli

+36-27
Original file line numberDiff line numberDiff line change
@@ -80,26 +80,22 @@
8080
detect that you've violated its well-formedness constraints and fail to
8181
parse the resulting AST. *)
8282

83-
(** Errors around the extension representation. These should mostly just be
84-
fatal, but they're needed for one test case
85-
(tests/ast-invariants/test.ml). *)
86-
module Error : sig
87-
(** Someone used [[%extension.EXTNAME]] wrong *)
88-
type malformed_extension =
89-
| Has_payload of Parsetree.payload
90-
91-
(** An error triggered when desugaring a language extension from an OCaml AST *)
92-
type error =
93-
| Malformed_extension of string list * malformed_extension
94-
| Unknown_extension of string
95-
| Disabled_extension of Language_extension.t
96-
| Wrong_syntactic_category of Language_extension.t * string
97-
| Unnamed_extension
98-
| Bad_introduction of string * string list
99-
100-
(** The main exception type thrown when desugaring a language extension from an
101-
OCaml AST; we also use the occasional [Misc.fatal_errorf]. *)
102-
exception Error of Location.t * error
83+
(** An AST-style representation of the names used when generating extension
84+
nodes for modular extensions. We use this to abstract over the details of
85+
how they're encoded, so we have some flexibility in changing them (although
86+
comments may refer to the specific encoding choices). This is also why we
87+
don't expose any functions for rendering or parsing these names; that's all
88+
handled internally. *)
89+
module Extension_node_name : sig
90+
(** A modular extension's extension node's name broken down into its
91+
components: the extension name plus any subparts. This is a nonempty list
92+
corresponding to the dot-separated components of the name, less
93+
[extension.]. *)
94+
type t = ( :: ) of string * string list
95+
96+
(** Print out a modular extension extension node name, in quotes; for use in
97+
error messages. *)
98+
val pp_quoted_name : Format.formatter -> t -> unit
10399
end
104100

105101
(** The type of modules that lift and lower language extension terms from and
@@ -131,7 +127,7 @@ module type AST = sig
131127
[extension.]. Any locations in the generated AST will be set to
132128
[!Ast_helper.default_loc], which should be [ghost]. Partial inverse of
133129
[match_extension]. *)
134-
val make_extension : string list -> ast -> ast_desc
130+
val make_extension : Extension_node_name.t -> ast -> ast_desc
135131

136132
(** As [make_extension], but specifically for the AST node corresponding to
137133
the entire piece of extension syntax (e.g., for a list comprehension, the
@@ -143,12 +139,12 @@ module type AST = sig
143139
loc:Location.t -> string -> (unit -> ast) -> ast_desc
144140

145141
(** Given an AST node, check if it's a language extension term; if it is,
146-
split it back up into its name (the [string list]) and the body (the
147-
[ast]); the resulting name is split on dots and the leading [extension]
148-
component is dropped. If the language extension term is malformed in any
149-
way, raises an error; if the input isn't a language extension term,
150-
returns [None]. Partial inverse of [make_extension]. *)
151-
val match_extension : ast -> (string list * ast) option
142+
split it back up into its name and the body; the resulting name is split
143+
on dots and the leading [extension] component is dropped. If the language
144+
extension term is malformed in any way, raises an error; if the input
145+
isn't a language extension term, returns [None]. Partial inverse of
146+
[make_extension]. *)
147+
val match_extension : ast -> (Extension_node_name.t * ast) option
152148
end
153149

154150
(** One [AST] module per syntactic category we currently care about; we're
@@ -214,3 +210,16 @@ end
214210
requires two extensions to be enabled at once (e.g., immutable array
215211
comprehensions such as [[:x for x = 1 to 10:]]). *)
216212
val assert_extension_enabled : loc:Location.t -> Language_extension.t -> unit
213+
214+
(** Errors around the extension representation. These should mostly just be
215+
fatal, but they're needed for one test case
216+
(language-extensions/language_extensions.ml). *)
217+
module Error : sig
218+
(** An error triggered when desugaring a language extension from an OCaml
219+
AST; left abstract because it should always be fatal *)
220+
type error
221+
222+
(** The exception type thrown when desugaring a language extension from an
223+
OCaml AST *)
224+
exception Error of Location.t * error
225+
end
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
File "user_error3.ml", line 21, characters 25-69:
22
21 | let _unknown_extension = [%extension.this_extension_doesn't_exist] ();;
33
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4-
Error: Unknown extension "this_extension_doesn't_exist" referenced via an
5-
[%extension.this_extension_doesn't_exist] extension node
4+
Error: Unknown extension "this_extension_doesn't_exist" referenced via
5+
an [%extension.this_extension_doesn't_exist] extension node
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
File "user_error5.ml", line 21, characters 25-40:
1+
File "user_error5.ml", line 21, characters 27-36:
22
21 | let _unnamed_extension = [%extension] ();;
3-
^^^^^^^^^^^^^^^
3+
^^^^^^^^^
44
Error: Cannot have an extension node named [%extension]
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
File "user_error6.ml", line 21, characters 24-56:
22
21 | let _bad_introduction = [%extension.something.nested] ();;
33
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4-
Error: The extension "something" was referenced improperly; it started with an
5-
[%extension.something.nested] extension node,
4+
Error: The extension "something" was referenced improperly; it started with
5+
an [%extension.something.nested] extension node,
66
not an [%extension.something] one

0 commit comments

Comments
 (0)