diff --git a/CHANGES.md b/CHANGES.md index 5be955e6a..b04b06a4f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ unreleased what to append to the current unit name in the presence of wrapping (#1788) - Add `-unboxed-types` and `-no-unboxed-types` as ocaml ignored flags (#1795, fixes #1794) - destruct: Refinement in the presence of optional arguments (#1800 #1807, fixes #1770) + - Implement new expand-node command for expanding PPX annotations (#1745) + editor modes - vim: fix python-3.12 syntax warnings in merlin.py (#1798) - vim: Dead code / doc removal for previously deleted MerlinPhrase command (#1804) diff --git a/src/analysis/ppx_expand.ml b/src/analysis/ppx_expand.ml new file mode 100644 index 000000000..2982ea78d --- /dev/null +++ b/src/analysis/ppx_expand.ml @@ -0,0 +1,159 @@ +type ppx_kind = + | Expr of Parsetree.expression + | Sig_item of Parsetree.signature_item + | Str_item of Parsetree.structure_item + +let check_at_pos pos loc = Location_aux.compare_pos pos loc = 0 + +let check_extension_node pos (expression : Parsetree.expression) = + match expression.pexp_desc with + | Pexp_extension (loc, _) -> + if check_at_pos pos loc.loc then Some expression.pexp_loc else None + | _ -> None + +let check_deriving_attr pos (attrs : Parsetree.attributes) = + let found_attr = + List.find_opt + (fun (attribute : Parsetree.attribute) -> + attribute.attr_name.txt = "deriving" + && check_at_pos pos attribute.attr_loc) + attrs + in + match found_attr with + | Some attribute -> Some attribute.attr_loc + | None -> None + +let check_structures pos (item : Parsetree.structure_item_desc) = + match item with + | Pstr_type (_, ty) -> + List.find_map + (fun (t : Parsetree.type_declaration) -> + check_deriving_attr pos t.ptype_attributes) + ty + | Pstr_exception tc -> check_deriving_attr pos tc.ptyexn_attributes + | Pstr_modtype mt -> check_deriving_attr pos mt.pmtd_attributes + | Pstr_typext tex -> check_deriving_attr pos tex.ptyext_attributes + | _ -> None + +let check_signatures pos (item : Parsetree.signature_item_desc) = + match item with + | Psig_type (_, ty) -> + List.find_map + (fun (t : Parsetree.type_declaration) -> + check_deriving_attr pos t.ptype_attributes) + ty + | Psig_exception tc -> check_deriving_attr pos tc.ptyexn_attributes + | Psig_modtype mt -> check_deriving_attr pos mt.pmtd_attributes + | Psig_typext tex -> check_deriving_attr pos tex.ptyext_attributes + | _ -> None + +let check_extension ~parsetree ~pos = + let kind = ref None in + let expr (self : Ast_iterator.iterator) (expr : Parsetree.expression) = + match check_extension_node pos expr with + | Some ext_loc -> kind := Some (Expr expr, ext_loc) + | None -> Ast_iterator.default_iterator.expr self expr + in + let signature_item (self : Ast_iterator.iterator) + (original_sg : Parsetree.signature_item) = + match check_signatures pos original_sg.psig_desc with + | Some attr_loc -> kind := Some (Sig_item original_sg, attr_loc) + | None -> Ast_iterator.default_iterator.signature_item self original_sg + in + let structure_item (self : Ast_iterator.iterator) + (original_str : Parsetree.structure_item) = + match check_structures pos original_str.pstr_desc with + | Some attr_loc -> kind := Some (Str_item original_str, attr_loc) + | None -> Ast_iterator.default_iterator.structure_item self original_str + in + let iterator = + { Ast_iterator.default_iterator with signature_item; structure_item; expr } + in + let () = + match parsetree with + | `Interface si -> iterator.signature iterator si + | `Implementation str -> iterator.structure iterator str + in + !kind + +let get_ppxed_source ~ppxed_parsetree ~pos ppx_kind_with_attr : + Query_protocol.ppxed_source = + let expression = ref None in + let signature = ref [] in + let structure = ref [] in + let () = + match ppx_kind_with_attr with + | Expr original_expr, _ -> ( + let expr (self : Ast_iterator.iterator) + (new_expr : Parsetree.expression) = + match + Location_aux.included ~into:original_expr.pexp_loc new_expr.pexp_loc + with + | true -> expression := Some new_expr + | false -> Ast_iterator.default_iterator.expr self new_expr + in + let iterator = { Ast_iterator.default_iterator with expr } in + match ppxed_parsetree with + | `Interface si -> iterator.signature iterator si + | `Implementation str -> iterator.structure iterator str) + | Sig_item original_sg, _ -> ( + let signature_item (self : Ast_iterator.iterator) + (new_sg : Parsetree.signature_item) = + let included = + Location_aux.included new_sg.psig_loc ~into:original_sg.psig_loc + in + match included && original_sg <> new_sg, new_sg.psig_loc.loc_ghost with + | true, _ -> signature := new_sg :: !signature + | false, false -> Ast_iterator.default_iterator.signature_item self new_sg + | false, true -> () (* We don't enter nested ppxes *) + in + let iterator = { Ast_iterator.default_iterator with signature_item } in + match ppxed_parsetree with + | `Interface si -> iterator.signature iterator si + | `Implementation str -> iterator.structure iterator str) + | Str_item original_str, _ -> ( + let structure_item (self : Ast_iterator.iterator) + (new_str : Parsetree.structure_item) = + let included = + Location_aux.included new_str.pstr_loc ~into:original_str.pstr_loc + in + match included, new_str.pstr_loc.loc_ghost with + | true, _ -> + (match check_structures pos new_str.pstr_desc with + | None -> structure := new_str :: !structure + | Some _ -> ()) + | false, false -> Ast_iterator.default_iterator.structure_item self new_str + | false, true -> () + in + let iterator = { Ast_iterator.default_iterator with structure_item } in + match ppxed_parsetree with + | `Interface si -> iterator.signature iterator si + | `Implementation str -> iterator.structure iterator str) + in + match (ppx_kind_with_attr : ppx_kind * Warnings.loc) with + | Expr _, ext_loc -> + { + code = Pprintast.string_of_expression (Option.get !expression); + attr_start = ext_loc.loc_start; + attr_end = ext_loc.loc_end; + } + | Sig_item _, attr_loc -> + let exp = + Pprintast.signature Format.str_formatter (List.rev !signature); + Format.flush_str_formatter () + in + { + code = exp; + attr_start = attr_loc.loc_start; + attr_end = attr_loc.loc_end; + } + | Str_item _, attr_loc -> + let exp = + Pprintast.structure Format.str_formatter (List.rev !structure); + Format.flush_str_formatter () + in + { + code = exp; + attr_start = attr_loc.loc_start; + attr_end = attr_loc.loc_end; + } diff --git a/src/analysis/ppx_expand.mli b/src/analysis/ppx_expand.mli new file mode 100644 index 000000000..2ba75c84e --- /dev/null +++ b/src/analysis/ppx_expand.mli @@ -0,0 +1,19 @@ +type ppx_kind = + | Expr of Parsetree.expression + | Sig_item of Parsetree.signature_item + | Str_item of Parsetree.structure_item + +val check_extension : + parsetree: + [ `Implementation of Parsetree.structure + | `Interface of Parsetree.signature ] -> + pos:Lexing.position -> + (ppx_kind * Warnings.loc) option + +val get_ppxed_source : + ppxed_parsetree: + [ `Implementation of Parsetree.structure + | `Interface of Parsetree.signature ] -> + pos:Lexing.position -> + ppx_kind * Warnings.loc -> + Query_protocol.ppxed_source diff --git a/src/commands/new_commands.ml b/src/commands/new_commands.ml index 4491ae9f2..1ce6ad57c 100644 --- a/src/commands/new_commands.ml +++ b/src/commands/new_commands.ml @@ -242,6 +242,21 @@ Otherwise, Merlin looks for the documentation for the entity under the cursor (a end ; + command "expand-ppx" + ~doc: "Returns the generated code of a PPX." + ~spec: [ + arg "-position" " Position to expand" + (marg_position (fun pos _pos -> pos)); + ] + ~default: `None + begin fun buffer pos -> + match pos with + | `None -> failwith "-position is mandatory" + | #Msource.position as pos -> + run buffer (Query_protocol.Expand_ppx pos) + end + ; + command "enclosing" ~spec: [ arg "-position" " Position to complete" diff --git a/src/commands/query_json.ml b/src/commands/query_json.ml index 69515bc1f..5b277a4b1 100644 --- a/src/commands/query_json.ml +++ b/src/commands/query_json.ml @@ -114,6 +114,8 @@ let dump (type a) : a t -> json = ] | Syntax_document pos -> mk "syntax-document" [ ("position", mk_position pos) ] + | Expand_ppx pos -> + mk "ppx-expand" [ ("position", mk_position pos) ] | Locate (prefix, look_for, pos) -> mk "locate" [ "prefix", (match prefix with @@ -392,6 +394,19 @@ let json_of_response (type a) (query : a t) (response : a) : json = ("url", `String info.documentation); ] | `No_documentation -> `String "No documentation found") + | Expand_ppx _, resp -> + let str = match resp with + | `Found ppx_info -> + `Assoc + [ + ("code", `String ppx_info.code); + ("deriver", `Assoc [ + ("start", Lexing.json_of_position ppx_info.attr_start); + ("end", Lexing.json_of_position ppx_info.attr_end); + ]) + ] + | `No_ppx -> `String "No PPX deriver/extension node found on this position" + in str | Locate_type _, resp -> json_of_locate resp | Locate _, resp -> json_of_locate resp | Jump _, resp -> diff --git a/src/frontend/query_commands.ml b/src/frontend/query_commands.ml index 350d3e758..c45e69185 100644 --- a/src/frontend/query_commands.ml +++ b/src/frontend/query_commands.ml @@ -512,6 +512,18 @@ let dispatch pipeline (type a) : a Query_protocol.t -> a = | Some res -> `Found res | None -> `No_documentation) + | Expand_ppx pos -> ( + let pos = Mpipeline.get_lexing_pos pipeline pos in + let parsetree = Mpipeline.reader_parsetree pipeline in + let ppxed_parsetree = Mpipeline.ppx_parsetree pipeline in + let ppx_kind_with_attr = Ppx_expand.check_extension ~parsetree ~pos in + match ppx_kind_with_attr with + | Some _ -> + `Found + (Ppx_expand.get_ppxed_source ~ppxed_parsetree ~pos + (Option.get ppx_kind_with_attr)) + | None -> `No_ppx) + | Locate (patho, ml_or_mli, pos) -> let typer = Mpipeline.typer_result pipeline in let local_defs = Mtyper.get_typedtree typer in diff --git a/src/frontend/query_protocol.ml b/src/frontend/query_protocol.ml index cd8871e47..73dfe9600 100644 --- a/src/frontend/query_protocol.ml +++ b/src/frontend/query_protocol.ml @@ -103,6 +103,13 @@ type syntax_doc_result = documentation : string } +type ppxed_source = +{ + code : string; + attr_start : Lexing.position; + attr_end : Lexing.position; +} + type is_tail_position = [`No | `Tail_position | `Tail_call] type _ _bool = bool @@ -145,6 +152,11 @@ type _ t = -> [ `Found of syntax_doc_result | `No_documentation ] t + | Expand_ppx + : Msource.position + -> [ `Found of ppxed_source + | `No_ppx + ] t | Locate_type : Msource.position -> [ `Found of string option * Lexing.position diff --git a/src/ocaml/parsing/location_aux.ml b/src/ocaml/parsing/location_aux.ml index 966ebdd3f..5a9ec92d8 100644 --- a/src/ocaml/parsing/location_aux.ml +++ b/src/ocaml/parsing/location_aux.ml @@ -46,6 +46,10 @@ let compare_pos pos loc = else 0 +let included ~into:parent_loc child_loc = +Lexing.compare_pos child_loc.loc_start parent_loc.loc_start >= 0 && + Lexing.compare_pos parent_loc.loc_end child_loc.loc_end >= 0 + let union l1 l2 = if l1 = Location.none then l2 else if l2 = Location.none then l1 diff --git a/src/ocaml/parsing/location_aux.mli b/src/ocaml/parsing/location_aux.mli index 7d99d36a0..d6164b2cd 100644 --- a/src/ocaml/parsing/location_aux.mli +++ b/src/ocaml/parsing/location_aux.mli @@ -42,6 +42,10 @@ val union : t -> t -> t (** Like location_union, but keep loc_ghost'ness of first argument *) val extend : t -> t -> t +(** [included ~into:parent child] returns [true] if [child] is included + in [parent]. Otherwise returns [false]. *) +val included : into:t -> t -> bool + (** Filter valid errors, log invalid ones *) val prepare_errors : exn list -> Location.error list diff --git a/tests/test-dirs/expand_node/ppx-tests.t/c_ppx/c_ppx.ml b/tests/test-dirs/expand_node/ppx-tests.t/c_ppx/c_ppx.ml new file mode 100644 index 000000000..8c0ec0b0f --- /dev/null +++ b/tests/test-dirs/expand_node/ppx-tests.t/c_ppx/c_ppx.ml @@ -0,0 +1,163 @@ +open Ppxlib +open Ast_builder.Default + +(* Type declarations in structure *) +let generate_impl ~ctxt (rec_flag, type_declarations) = + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + List.map (fun ty -> + pstr_type ~loc rec_flag + [{ + ptype_loc = {loc with loc_ghost = true}; + ptype_params = ty.ptype_params; + ptype_cstrs = ty.ptype_cstrs; + ptype_kind = ty.ptype_kind; + ptype_manifest = ty.ptype_manifest; + ptype_private = ty.ptype_private; + ptype_attributes = []; + ptype_name = {txt = ty.ptype_name.txt ^ "_renamed"; loc = {loc with loc_ghost = true}} + }] + ) type_declarations + +(* Type declarations in signature *) +let generate_intf ~ctxt (rec_flag, type_declarations) = + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + List.map (fun ty -> + psig_type ~loc rec_flag + [{ + ptype_loc = {loc with loc_ghost = true}; + ptype_params = ty.ptype_params; + ptype_cstrs = ty.ptype_cstrs; + ptype_kind = ty.ptype_kind; + ptype_manifest = ty.ptype_manifest; + ptype_private = ty.ptype_private; + ptype_attributes = []; + ptype_name = {txt = ty.ptype_name.txt ^ "_renamed"; loc = {loc with loc_ghost = true}} + }] + ) type_declarations + +(* Type_extensions in structure *) +let generate_ext_impl ~ctxt type_extension = + let new_path = Longident.parse ((Longident.name type_extension.ptyext_path.txt) ^ "_renamed") in + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + [ + pstr_typext ~loc + { + ptyext_path = {txt = new_path; loc = ({loc with loc_ghost = true})}; + ptyext_params = type_extension.ptyext_params; + ptyext_constructors = type_extension.ptyext_constructors; + ptyext_private = type_extension.ptyext_private; + ptyext_loc = {type_extension.ptyext_loc with loc_ghost = true}; + ptyext_attributes = []; + } + ] + +(* Type_extensions in signature *) +let generate_ext_intf ~ctxt type_extension = + let new_path = Longident.parse ((Longident.name type_extension.ptyext_path.txt) ^ "_renamed") in + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + [ + psig_typext ~loc + { + ptyext_path = {txt = new_path; loc = ({loc with loc_ghost = true})}; + ptyext_params = type_extension.ptyext_params; + ptyext_constructors = type_extension.ptyext_constructors; + ptyext_private = type_extension.ptyext_private; + ptyext_loc = {type_extension.ptyext_loc with loc_ghost = true}; + ptyext_attributes = []; + } + ] + +(* Type_exceptions in structure *) +let generate_exn_impl ~ctxt type_exception = + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + [ + pstr_exception ~loc + { + ptyexn_constructor = { + pext_name = {txt = type_exception.ptyexn_constructor.pext_name.txt ^ "_renamed"; loc = {loc with loc_ghost = true}}; + pext_kind = type_exception.ptyexn_constructor.pext_kind; + pext_loc = {loc with loc_ghost = true}; + pext_attributes = []; + }; + ptyexn_loc = {loc with loc_ghost = true}; + ptyexn_attributes = []; + } + ] + +(* Type_exceptions in signature *) +let generate_exn_intf ~ctxt type_exception = + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + [ + psig_exception ~loc + { + ptyexn_constructor = { + pext_name = {txt = type_exception.ptyexn_constructor.pext_name.txt ^ "_renamed"; loc = {loc with loc_ghost = true}}; + pext_kind = type_exception.ptyexn_constructor.pext_kind; + pext_loc = {loc with loc_ghost = true}; + pext_attributes = []; + }; + ptyexn_loc = {loc with loc_ghost = true}; + ptyexn_attributes = []; + } + ] + +(* Module_type_declarations in structure *) +let generate_mt_impl ~ctxt module_type_declaration = + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + [ + pstr_modtype ~loc + { + pmtd_name = {txt = module_type_declaration.pmtd_name.txt ^ "_renamed"; loc = {loc with loc_ghost = true};}; + pmtd_type = module_type_declaration.pmtd_type; + pmtd_attributes = []; + pmtd_loc = {loc with loc_ghost = true}; + } + ] + +let generate_mt_intf ~ctxt module_type_declaration = + let loc = Expansion_context.Deriver.derived_item_loc ctxt in + [ + psig_modtype ~loc + { + pmtd_name = {txt = module_type_declaration.pmtd_name.txt ^ "_renamed"; loc = {loc with loc_ghost = true};}; + pmtd_type = module_type_declaration.pmtd_type; + pmtd_attributes = []; + pmtd_loc = {loc with loc_ghost = true}; + } + ] + + +(* Driver for type declarations in structures*) +let ty_impl_generator = Deriving.Generator.V2.make_noarg generate_impl + +(* Driver for type declarations in signatures*) +let ty_intf_generator = Deriving.Generator.V2.make_noarg generate_intf + +(* Driver for type_extensions in structures*) +let ext_impl_generator = Deriving.Generator.V2.make_noarg generate_ext_impl + +(* Driver for type_extensions in signatures*) +let ext_intf_generator = Deriving.Generator.V2.make_noarg generate_ext_intf + +(* Driver for type_exceptions in structures*) +let exn_impl_generator = Deriving.Generator.V2.make_noarg generate_exn_impl + +(* Driver for type_exceptions in signatures*) +let exn_intf_generator = Deriving.Generator.V2.make_noarg generate_exn_intf + +(* Driver for module_type_declarations in structures*) +let mdt_impl_generator = Deriving.Generator.V2.make_noarg generate_mt_impl + +(* Driver for module_type_declarations in signatures*) +let mdt_intf_generator = Deriving.Generator.V2.make_noarg generate_mt_intf +let my_deriver = + Deriving.add "rename" + ~str_type_decl:ty_impl_generator + ~sig_type_decl:ty_intf_generator + ~str_type_ext:ext_impl_generator + ~sig_type_ext:ext_intf_generator + ~str_exception:exn_impl_generator + ~sig_exception:exn_intf_generator + ~str_module_type_decl:mdt_impl_generator + ~sig_module_type_decl:mdt_intf_generator + |> Deriving.ignore diff --git a/tests/test-dirs/expand_node/ppx-tests.t/c_ppx/dune b/tests/test-dirs/expand_node/ppx-tests.t/c_ppx/dune new file mode 100644 index 000000000..c6fb4575a --- /dev/null +++ b/tests/test-dirs/expand_node/ppx-tests.t/c_ppx/dune @@ -0,0 +1,4 @@ +(library + (name c_ppx) + (kind ppx_deriver) + (libraries ppxlib)) diff --git a/tests/test-dirs/expand_node/ppx-tests.t/rewriter/dune b/tests/test-dirs/expand_node/ppx-tests.t/rewriter/dune new file mode 100644 index 000000000..fcbdc1e39 --- /dev/null +++ b/tests/test-dirs/expand_node/ppx-tests.t/rewriter/dune @@ -0,0 +1,4 @@ +(library + (name my_ppx) + (kind ppx_rewriter) + (libraries ppxlib)) diff --git a/tests/test-dirs/expand_node/ppx-tests.t/rewriter/my_ppx.ml b/tests/test-dirs/expand_node/ppx-tests.t/rewriter/my_ppx.ml new file mode 100644 index 000000000..797578f42 --- /dev/null +++ b/tests/test-dirs/expand_node/ppx-tests.t/rewriter/my_ppx.ml @@ -0,0 +1,14 @@ +open Ppxlib + +let expand ~ctxt payload = + let _p = payload in + let loc = Expansion_context.Extension.extension_point_loc ctxt in + Ast_builder.Default.estring ~loc "OCaml is so cool" + +let my_extension = + Extension.V3.declare "tell_me" Extension.Context.expression + Ast_pattern.(__) + expand + +let rule = Ppxlib.Context_free.Rule.extension my_extension +let () = Driver.register_transformation ~rules:[ rule ] "tell_me" diff --git a/tests/test-dirs/expand_node/ppx-tests.t/run.t b/tests/test-dirs/expand_node/ppx-tests.t/run.t new file mode 100644 index 000000000..848f9a8c1 --- /dev/null +++ b/tests/test-dirs/expand_node/ppx-tests.t/run.t @@ -0,0 +1,587 @@ +Dune setup + $ cat > dune-project << EOF + > (lang dune 2.9) + > EOF + + $ cat > dune << EOF + > (executable + > (name apt) + > (preprocess (pps c_ppx my_ppx))) + > EOF + +Type declaration in structure + $ cat > apt.ml << EOF + > module MyModule = struct + > type point = {x:int; y:int} [@@deriving rename] + > end + > EOF + + $ dune build + +on module name "MyModule" + $ $MERLIN single expand-ppx -position 1:11 -filename ./apt.ml < ./apt.ml + { + "class": "return", + "value": "No PPX deriver/extension node found on this position", + "notifications": [] + } + +on keyword type + $ $MERLIN single expand-ppx -position 2:3 -filename ./apt.ml < ./apt.ml + { + "class": "return", + "value": "No PPX deriver/extension node found on this position", + "notifications": [] + } + +on attribute name "deriving" + $ $MERLIN single expand-ppx -position 2:36 -filename ./apt.ml < ./apt.ml + { + "class": "return", + "value": { + "code": "include + struct + let _ = fun (_ : point) -> () + type point_renamed = { + x: int ; + y: int } + end[@@ocaml.doc \"@inline\"][@@merlin.hide ]", + "deriver": { + "start": { + "line": 2, + "col": 29 + }, + "end": { + "line": 2, + "col": 48 + } + } + }, + "notifications": [] + } + +on attribute payload name "rename" + $ $MERLIN single expand-ppx -position 2:46 -filename ./apt.ml < ./apt.ml + { + "class": "return", + "value": { + "code": "include + struct + let _ = fun (_ : point) -> () + type point_renamed = { + x: int ; + y: int } + end[@@ocaml.doc \"@inline\"][@@merlin.hide ]", + "deriver": { + "start": { + "line": 2, + "col": 29 + }, + "end": { + "line": 2, + "col": 48 + } + } + }, + "notifications": [] + } + +Type declaration in signature + $ cat > apt.ml << EOF + > module type MyModuleSig = sig + > type tttt = Red | Green [@@deriving rename] + > end + > EOF + + $ dune build + +on attribute name "deriving" + $ $MERLIN single expand-ppx -position 2:36 -filename ./apt.ml < ./apt.ml + { + "class": "return", + "value": { + "code": "include sig [@@@ocaml.warning \"-32\"] type tttt_renamed = + | Red + | Green end[@@ocaml.doc \"@inline\"] + [@@merlin.hide ]", + "deriver": { + "start": { + "line": 2, + "col": 26 + }, + "end": { + "line": 2, + "col": 45 + } + } + }, + "notifications": [] + } + +on attribute payload name "rename" + $ $MERLIN single expand-ppx -position 2:42 -filename ./apt.ml < ./apt.ml + { + "class": "return", + "value": { + "code": "include sig [@@@ocaml.warning \"-32\"] type tttt_renamed = + | Red + | Green end[@@ocaml.doc \"@inline\"] + [@@merlin.hide ]", + "deriver": { + "start": { + "line": 2, + "col": 26 + }, + "end": { + "line": 2, + "col": 45 + } + } + }, + "notifications": [] + } + +Type declaration in structure + $ cat > apt.ml << EOF + > type yyyy = int [@@deriving rename] + > EOF + + $ dune build + + $ $MERLIN single expand-ppx -position 1:23 -filename ./apt.ml < ./apt.ml + { + "class": "return", + "value": { + "code": "include struct let _ = fun (_ : yyyy) -> () + type yyyy_renamed = int end[@@ocaml.doc \"@inline\"][@@merlin.hide + ]", + "deriver": { + "start": { + "line": 1, + "col": 16 + }, + "end": { + "line": 1, + "col": 35 + } + } + }, + "notifications": [] + } + +Type declaration in signature + $ cat > apt.mli << EOF + > type yyyy = int [@@deriving rename] + > EOF + + $ dune build + + $ $MERLIN single expand-ppx -position 1:23 -filename ./apt.mli < ./apt.mli + { + "class": "return", + "value": { + "code": "include sig [@@@ocaml.warning \"-32\"] type yyyy_renamed = int end[@@ocaml.doc + \"@inline\"] + [@@merlin.hide ]", + "deriver": { + "start": { + "line": 1, + "col": 16 + }, + "end": { + "line": 1, + "col": 35 + } + } + }, + "notifications": [] + } + +Type extension in structure + $ cat > apy.ml << EOF + > type pppp = .. [@@deriving rename] + > EOF + + $ dune build + + $ $MERLIN single expand-ppx -position 1:22 -filename ./apy.ml < ./apy.ml + { + "class": "return", + "value": { + "code": "include struct let _ = fun (_ : pppp) -> () + type pppp_renamed = .. end[@@ocaml.doc \"@inline\"][@@merlin.hide + ]", + "deriver": { + "start": { + "line": 1, + "col": 15 + }, + "end": { + "line": 1, + "col": 34 + } + } + }, + "notifications": [] + } + +Type extension in signature + $ cat > apy.mli << EOF + > type pppp = .. [@@deriving rename] + > EOF + + $ dune build + + $ $MERLIN single expand-ppx -position 1:22 -filename ./apy.mli < ./apy.mli + { + "class": "return", + "value": { + "code": "include sig [@@@ocaml.warning \"-32\"] type pppp_renamed = .. end[@@ocaml.doc + \"@inline\"] + [@@merlin.hide ]", + "deriver": { + "start": { + "line": 1, + "col": 15 + }, + "end": { + "line": 1, + "col": 34 + } + } + }, + "notifications": [] + } + +Exception in structure + $ cat > apr.ml << EOF + > exception Foo of string [@@deriving rename] + > EOF + + $ dune build + + $ $MERLIN single expand-ppx -position 1:30 -filename ./apr.ml < ./apr.ml + { + "class": "return", + "value": { + "code": "include struct exception Foo_renamed of string end[@@ocaml.doc \"@inline\"] + [@@merlin.hide ]", + "deriver": { + "start": { + "line": 1, + "col": 24 + }, + "end": { + "line": 1, + "col": 43 + } + } + }, + "notifications": [] + } + +Exception in signature + $ cat > apr.mli << EOF + > exception Foo of string [@@deriving rename] + > EOF + + $ dune build + + $ $MERLIN single expand-ppx -position 1:30 -filename ./apr.mli < ./apr.mli + { + "class": "return", + "value": { + "code": "include sig [@@@ocaml.warning \"-32\"] exception Foo_renamed of string end + [@@ocaml.doc \"@inline\"][@@merlin.hide ]", + "deriver": { + "start": { + "line": 1, + "col": 24 + }, + "end": { + "line": 1, + "col": 43 + } + } + }, + "notifications": [] + } + +Module type declaration in structure + $ cat > apc.ml << EOF + > module type Stack = sig + > type t [@@deriving rename] + > type stack + > val empty : stack + > val is_empty : stack -> bool + > val push : t -> stack -> stack + > val pop : stack -> stack + > val peek : stack -> t + > end [@@deriving rename] + > EOF + + $ dune build + +a cursor here should only output the derived t + +(* Type t_renamed is duplicated multiple times because the same type is derived twice, first by it's own ppx and secondly +when the parent ppx on the module type declaration is evaluated. *) + $ $MERLIN single expand-ppx -position 2:14 -filename ./apc.ml < ./apc.ml + { + "class": "return", + "value": { + "code": "include sig [@@@ocaml.warning \"-32\"] type t_renamed end[@@ocaml.doc + \"@inline\"][@@merlin.hide + ] + include sig [@@@ocaml.warning \"-32\"] type t_renamed end[@@ocaml.doc + \"@inline\"][@@merlin.hide + ] + include sig [@@@ocaml.warning \"-32\"] type t_renamed end[@@ocaml.doc + \"@inline\"][@@merlin.hide + ]", + "deriver": { + "start": { + "line": 2, + "col": 9 + }, + "end": { + "line": 2, + "col": 28 + } + } + }, + "notifications": [] + } + +(* Type t_renamed is duplicated multiple times because the same type is derived twice, first by it's own ppx and secondly +when the parent ppx on the module type declaration is evaluated. *) + + $ $MERLIN single expand-ppx -position 9:8 -filename ./apc.ml < ./apc.ml + { + "class": "return", + "value": { + "code": "include + struct + module type Stack_renamed = + sig + type t[@@deriving rename] + include sig [@@@ocaml.warning \"-32\"] type t_renamed end[@@ocaml.doc + \"@inline\"] + [@@merlin.hide ] + include sig [@@@ocaml.warning \"-32\"] type t_renamed end[@@ocaml.doc + \"@inline\"] + [@@merlin.hide ] + type stack + val empty : stack + val is_empty : stack -> bool + val push : t -> stack -> stack + val pop : stack -> stack + val peek : stack -> t + end + end[@@ocaml.doc \"@inline\"][@@merlin.hide ]", + "deriver": { + "start": { + "line": 9, + "col": 4 + }, + "end": { + "line": 9, + "col": 23 + } + } + }, + "notifications": [] + } + +Module type declaration in signature + $ cat > apc.mli << EOF + > module type Stack = sig + > type t [@@deriving rename] + > type stack + > val empty : stack + > val is_empty : stack -> bool + > val push : t -> stack -> stack + > val pop : stack -> stack + > val peek : stack -> t + > end [@@deriving rename] + > EOF + + $ dune build + +on attribute name deriving of type t +a cursor here should only output the derived t + $ $MERLIN single expand-ppx -position 2:14 -filename ./apc.mli < ./apc.mli + { + "class": "return", + "value": { + "code": "include sig [@@@ocaml.warning \"-32\"] type t_renamed end[@@ocaml.doc + \"@inline\"][@@merlin.hide + ]", + "deriver": { + "start": { + "line": 2, + "col": 9 + }, + "end": { + "line": 2, + "col": 28 + } + } + }, + "notifications": [] + } + +on attribute name deriving of module Stack +(* Type t_renamed is duplicated multiple times because the same type is derived twice, first by it's own ppx and secondly +when the parent ppx on the module type declaration is evaluated. *) + $ $MERLIN single expand-ppx -position 9:8 -filename ./apc.mli < ./apc.mli + { + "class": "return", + "value": { + "code": "module type Stack = + sig + type t[@@deriving rename] + include sig [@@@ocaml.warning \"-32\"] type t_renamed end[@@ocaml.doc + \"@inline\"] + [@@merlin.hide ] + type stack + val empty : stack + val is_empty : stack -> bool + val push : t -> stack -> stack + val pop : stack -> stack + val peek : stack -> t + end[@@deriving rename] + include + sig + [@@@ocaml.warning \"-32\"] + module type Stack_renamed = + sig + type t[@@deriving rename] + include sig [@@@ocaml.warning \"-32\"] type t_renamed end[@@ocaml.doc + \"@inline\"] + [@@merlin.hide ] + include sig [@@@ocaml.warning \"-32\"] type t_renamed end[@@ocaml.doc + \"@inline\"] + [@@merlin.hide ] + type stack + val empty : stack + val is_empty : stack -> bool + val push : t -> stack -> stack + val pop : stack -> stack + val peek : stack -> t + end + end[@@ocaml.doc \"@inline\"][@@merlin.hide ]", + "deriver": { + "start": { + "line": 9, + "col": 4 + }, + "end": { + "line": 9, + "col": 23 + } + } + }, + "notifications": [] + } + +Test for an attribute that's not deriving + $ cat > apf.ml << EOF + > type y = int * float [@@merlin.hide] + > EOF + + $ dune build + + $ $MERLIN single expand-ppx -position 1:27 -filename ./apf.ml < ./apf.ml + { + "class": "return", + "value": "No PPX deriver/extension node found on this position", + "notifications": [] + } + +PPx extension + $ cat > apttt.ml << EOF + > let phrase = print_string ([%tell_me] ^ ":-)!") + > EOF + + $ dune build + + $ $MERLIN single expand-ppx -position 1:30 -filename ./apttt.ml < ./apttt.ml + { + "class": "return", + "value": { + "code": "\"OCaml is so cool\"", + "deriver": { + "start": { + "line": 1, + "col": 27 + }, + "end": { + "line": 1, + "col": 37 + } + } + }, + "notifications": [] + } + + $ $MERLIN single expand-ppx -position 1:41 -filename ./apttt.ml < ./apttt.ml + { + "class": "return", + "value": "No PPX deriver/extension node found on this position", + "notifications": [] + } + +Show only an output for the hover and not all extensions + $ cat > aptxc.ml << EOF + > let phrase = [%tell_me] ^ [%tell_me] + > EOF + + $ dune build +on the first [%tell_me] + $ $MERLIN single expand-ppx -position 1:16 -filename ./apttt.ml < ./aptxc.ml + { + "class": "return", + "value": { + "code": "\"OCaml is so cool\"", + "deriver": { + "start": { + "line": 1, + "col": 13 + }, + "end": { + "line": 1, + "col": 23 + } + } + }, + "notifications": [] + } + +on the concatenator + $ $MERLIN single expand-ppx -position 1:24 -filename ./apttt.ml < ./aptxc.ml + { + "class": "return", + "value": "No PPX deriver/extension node found on this position", + "notifications": [] + } + +on the second [%tell_me] + $ $MERLIN single expand-ppx -position 1:28 -filename ./apttt.ml < ./aptxc.ml + { + "class": "return", + "value": { + "code": "\"OCaml is so cool\"", + "deriver": { + "start": { + "line": 1, + "col": 26 + }, + "end": { + "line": 1, + "col": 36 + } + } + }, + "notifications": [] + }