Validate configuration and diagnose unsupported record declarations
This commit is contained in:
2 files changed
+67
-3
No files matched your search
@@ -17,6 +17,21 @@ reject() {
|
|||||||
|
|
||||||
reject 'type t = { colour : string } [@@fieldglass generate ~no_get]
|
reject 'type t = { colour : string } [@@fieldglass generate ~no_get]
|
||||||
let _ = colour' 'Unbound value colour'
|
let _ = colour' 'Unbound value colour'
|
||||||
|
reject 'type t = A [@@fieldglass generate]' 'fieldglass: expected a record'
|
||||||
|
reject 'type t = { colour : string } [@@fieldglass invalid]' 'fieldglass: expected generate'
|
||||||
|
reject 'type t = { colour : string } [@@fieldglass generate ~unknown]' 'fieldglass: unrecognised'
|
||||||
|
reject 'type t = { colour : string } [@@fieldglass generate ~no_get:3]' 'fieldglass: expected a boolean'
|
||||||
|
reject 'type t = { colour : string } [@@fieldglass generate ~field_prefix:3]' 'fieldglass: expected an identifier'
|
||||||
|
reject 'type t = { colour : string } [@@fieldglass generate ~field_prefix:"a b"]' 'fieldglass: invalid generated identifier'
|
||||||
|
reject 'type t = { colour : string } [@@fieldglass generate ~func_named_arg:"type"]' 'fieldglass: invalid generated identifier'
|
||||||
|
reject 'type t = { colour : string } [@@fieldglass generate ~no_get ~no_get]' 'fieldglass: duplicate option'
|
||||||
|
reject 'type t = { colour : string } [@@fieldglass generate 3]' 'fieldglass: expected a labelled option'
|
||||||
|
reject 'type t = { colour : string } [@@fieldglass generate] [@@lens generate]' 'fieldglass: expected'
|
||||||
|
reject 'type t = { x : int; set_x : int } [@@fieldglass generate]' 'fieldglass: duplicate generated binding'
|
||||||
|
reject 'type a = { value : int } and b = { value : string } [@@fieldglass generate]' 'fieldglass: duplicate generated binding'
|
||||||
|
reject 'type t = private { colour : string } [@@fieldglass generate]' 'fieldglass: private records'
|
||||||
|
reject "type t = { apply : 'a. 'a -> 'a } [@@fieldglass generate]" 'fieldglass: polymorphic fields'
|
||||||
|
reject 'module type S = sig type t = { colour : string } [@@fieldglass generate] end' 'fieldglass: generate accessors in an implementation'
|
||||||
reject 'type t = { colour : string } [@@fieldglass generate ~no_set]
|
reject 'type t = { colour : string } [@@fieldglass generate ~no_set]
|
||||||
let _ = set_colour' 'Unbound value set_colour'
|
let _ = set_colour' 'Unbound value set_colour'
|
||||||
reject 'type t = { colour : string } [@@fieldglass generate ~no_update]
|
reject 'type t = { colour : string } [@@fieldglass generate ~no_update]
|
||||||
|
|||||||
+52
-3
@@ -9,6 +9,16 @@ let lambda ~loc label pattern body = Exp.fun_ ~loc label None pattern body
|
|||||||
|
|
||||||
let is_marker (name, _) = name.txt = "fieldglass" || name.txt = "lens"
|
let is_marker (name, _) = name.txt = "fieldglass" || name.txt = "lens"
|
||||||
|
|
||||||
|
let check_identifier ~loc name =
|
||||||
|
let input = Lexing.from_string name in
|
||||||
|
let valid = try
|
||||||
|
match Lexer.token input with
|
||||||
|
| Parser.LIDENT parsed when parsed = name -> Lexer.token input = Parser.EOF
|
||||||
|
| _ -> false
|
||||||
|
with Lexer.Error _ -> false in
|
||||||
|
if not valid then Location.raise_errorf ~loc
|
||||||
|
"fieldglass: invalid generated identifier %S" name
|
||||||
|
|
||||||
type prefix = Plain | Named of string | Type_name
|
type prefix = Plain | Named of string | Type_name
|
||||||
type options = {
|
type options = {
|
||||||
field_prefix : prefix;
|
field_prefix : prefix;
|
||||||
@@ -51,7 +61,10 @@ let configure options (label, expression) =
|
|||||||
| Labelled "set_prefix" -> { options with set_prefix = word expression }
|
| Labelled "set_prefix" -> { options with set_prefix = word expression }
|
||||||
| Labelled "update_prefix" -> { options with update_prefix = word expression }
|
| Labelled "update_prefix" -> { options with update_prefix = word expression }
|
||||||
| Labelled "self_arg_first" -> { options with self_first = flag "self_arg_first" expression }
|
| Labelled "self_arg_first" -> { options with self_first = flag "self_arg_first" expression }
|
||||||
| Labelled "func_named_arg" -> { options with function_label = Some (word expression) }
|
| Labelled "func_named_arg" ->
|
||||||
|
let name = word expression in
|
||||||
|
check_identifier ~loc:expression.pexp_loc name;
|
||||||
|
{ options with function_label = Some name }
|
||||||
| Labelled "func_no_named_arg" ->
|
| Labelled "func_no_named_arg" ->
|
||||||
{ options with function_label = if flag "func_no_named_arg" expression then None else Some "f" }
|
{ options with function_label = if flag "func_no_named_arg" expression then None else Some "f" }
|
||||||
| Labelled "no_get" -> { options with getters = not (flag "no_get" expression) }
|
| Labelled "no_get" -> { options with getters = not (flag "no_get" expression) }
|
||||||
@@ -73,6 +86,15 @@ let configured declaration =
|
|||||||
| Pexp_apply ({ pexp_desc = Pexp_ident { txt = Longident.Lident "generate"; _ }; _ }, arguments) -> arguments
|
| Pexp_apply ({ pexp_desc = Pexp_ident { txt = Longident.Lident "generate"; _ }; _ }, arguments) -> arguments
|
||||||
| _ -> Location.raise_errorf ~loc:expression.pexp_loc
|
| _ -> Location.raise_errorf ~loc:expression.pexp_loc
|
||||||
"fieldglass: expected generate followed by labelled options" in
|
"fieldglass: expected generate followed by labelled options" in
|
||||||
|
let seen = Hashtbl.create 8 in
|
||||||
|
List.iter (fun (label, argument) ->
|
||||||
|
match label with
|
||||||
|
| Labelled name ->
|
||||||
|
if Hashtbl.mem seen name then Location.raise_errorf ~loc:argument.pexp_loc
|
||||||
|
"fieldglass: duplicate option %s" name;
|
||||||
|
Hashtbl.add seen name ()
|
||||||
|
| _ -> Location.raise_errorf ~loc:argument.pexp_loc
|
||||||
|
"fieldglass: expected a labelled option") arguments;
|
||||||
Some arguments
|
Some arguments
|
||||||
| _ -> Location.raise_errorf ~loc:declaration.ptype_loc
|
| _ -> Location.raise_errorf ~loc:declaration.ptype_loc
|
||||||
"fieldglass: expected [@@fieldglass generate]"
|
"fieldglass: expected [@@fieldglass generate]"
|
||||||
@@ -98,6 +120,13 @@ let generate options declaration =
|
|||||||
| Ptype_record fields -> fields
|
| Ptype_record fields -> fields
|
||||||
| _ -> Location.raise_errorf ~loc "fieldglass: expected a record type"
|
| _ -> Location.raise_errorf ~loc "fieldglass: expected a record type"
|
||||||
in
|
in
|
||||||
|
let writes = options.setters || options.updaters || options.lenses in
|
||||||
|
if declaration.ptype_private = Private && writes then
|
||||||
|
Location.raise_errorf ~loc "fieldglass: private records only support getters";
|
||||||
|
List.iter (fun field -> match field.pld_type.ptyp_desc with
|
||||||
|
| Ptyp_poly (_ :: _, _) when writes -> Location.raise_errorf ~loc:field.pld_loc
|
||||||
|
"fieldglass: polymorphic fields only support getters"
|
||||||
|
| _ -> ()) fields;
|
||||||
let source = value ~loc "__fg_source" in
|
let source = value ~loc "__fg_source" in
|
||||||
let record_type () = Typ.constr ~loc (ident ~loc declaration.ptype_name.txt)
|
let record_type () = Typ.constr ~loc (ident ~loc declaration.ptype_name.txt)
|
||||||
(List.map (fun _ -> Typ.any ~loc ()) declaration.ptype_params) in
|
(List.map (fun _ -> Typ.any ~loc ()) declaration.ptype_params) in
|
||||||
@@ -125,7 +154,10 @@ let generate options declaration =
|
|||||||
(replace (Exp.apply ~loc (value ~loc "__fg_function")
|
(replace (Exp.apply ~loc (value ~loc "__fg_function")
|
||||||
[Nolabel, Exp.field ~loc source (ident ~loc name)])) in
|
[Nolabel, Exp.field ~loc source (ident ~loc name)])) in
|
||||||
List.concat (List.map (fun (enabled, name, body) ->
|
List.concat (List.map (fun (enabled, name, body) ->
|
||||||
if enabled then [Str.value ~loc Nonrecursive [Vb.mk ~loc (variable ~loc name) body]] else [])
|
if enabled then begin
|
||||||
|
check_identifier ~loc name;
|
||||||
|
[Str.value ~loc Nonrecursive [Vb.mk ~loc (variable ~loc name) body]]
|
||||||
|
end else [])
|
||||||
[options.getters, (match options.get_prefix with None -> short_name | Some prefix -> prefix ^ "_" ^ short_name), getter;
|
[options.getters, (match options.get_prefix with None -> short_name | Some prefix -> prefix ^ "_" ^ short_name), getter;
|
||||||
options.setters, options.set_prefix ^ "_" ^ short_name, setter options.self_first;
|
options.setters, options.set_prefix ^ "_" ^ short_name, setter options.self_first;
|
||||||
options.updaters, options.update_prefix ^ "_" ^ short_name, updater;
|
options.updaters, options.update_prefix ^ "_" ^ short_name, updater;
|
||||||
@@ -143,11 +175,28 @@ let structure mapper items =
|
|||||||
let options = List.fold_left configure defaults arguments in
|
let options = List.fold_left configure defaults arguments in
|
||||||
List.concat (List.map (generate options) declarations)
|
List.concat (List.map (generate options) declarations)
|
||||||
else [] in
|
else [] in
|
||||||
|
let names = Hashtbl.create 16 in
|
||||||
|
List.iter (fun item -> match item.pstr_desc with
|
||||||
|
| Pstr_value (_, [{ pvb_pat = { ppat_desc = Ppat_var name; _ }; _ }]) ->
|
||||||
|
if Hashtbl.mem names name.txt then Location.raise_errorf ~loc:name.loc
|
||||||
|
"fieldglass: duplicate generated binding %s; customise the prefixes" name.txt;
|
||||||
|
Hashtbl.add names name.txt ()
|
||||||
|
| _ -> ()) generated;
|
||||||
let declarations = List.map (fun declaration ->
|
let declarations = List.map (fun declaration ->
|
||||||
{ declaration with ptype_attributes =
|
{ declaration with ptype_attributes =
|
||||||
List.filter (fun attribute -> not (is_marker attribute)) declaration.ptype_attributes }) declarations in
|
List.filter (fun attribute -> not (is_marker attribute)) declaration.ptype_attributes }) declarations in
|
||||||
{ item with pstr_desc = Pstr_type (recursive, declarations) } :: generated
|
{ item with pstr_desc = Pstr_type (recursive, declarations) } :: generated
|
||||||
| _ -> [item]) items)
|
| _ -> [item]) items)
|
||||||
|
|
||||||
|
let signature_item mapper item =
|
||||||
|
(match item.psig_desc with
|
||||||
|
| Psig_type (_, declarations) ->
|
||||||
|
List.iter (fun declaration ->
|
||||||
|
if List.exists is_marker declaration.ptype_attributes then
|
||||||
|
Location.raise_errorf ~loc:declaration.ptype_loc
|
||||||
|
"fieldglass: generate accessors in an implementation, not a signature") declarations
|
||||||
|
| _ -> ());
|
||||||
|
Ast_mapper.default_mapper.signature_item mapper item
|
||||||
|
|
||||||
let () = Ast_mapper.register "fieldglass"
|
let () = Ast_mapper.register "fieldglass"
|
||||||
(fun _ -> { Ast_mapper.default_mapper with structure })
|
(fun _ -> { Ast_mapper.default_mapper with structure; signature_item })
|
||||||
Reference in new issue
Block a user