diff --git a/src/analysis/completion.ml b/src/analysis/completion.ml index dfc83915e..6ca30cad6 100644 --- a/src/analysis/completion.ml +++ b/src/analysis/completion.ml @@ -133,6 +133,15 @@ let classify_node = function | Open_declaration _ -> `Module | Include_declaration _ -> `Module | Include_description _ -> `Module + | Mode _ | Modality _ -> + (* CR-someday: Have proper completion for modes and modalities *) + `Expression + | Jkind_annotation _ -> + (* CR-someday: Have proper completion for jkinds *) + `Type + | Attribute _ -> + (* CR-someday: Have proper completion for attributes *) + `Expression open Query_protocol.Compl diff --git a/src/analysis/syntax_doc.ml b/src/analysis/syntax_doc.ml index fe876fea8..f72d33a39 100644 --- a/src/analysis/syntax_doc.ml +++ b/src/analysis/syntax_doc.ml @@ -1,12 +1,772 @@ open Browse_raw +open Std -type syntax_info = Query_protocol.syntax_doc_result option +type syntax_info = Query_protocol.Syntax_doc_result.t option -let syntax_doc_url endpoint = - let base_url = "https://v2.ocaml.org/releases/4.14/htmlman/" in - base_url ^ endpoint +module Doc_website_base = struct + type t = Ocaml | Oxcaml +end + +let syntax_doc_url (doc_website_base : Doc_website_base.t) endpoint = + let base_url = + match doc_website_base with + | Ocaml -> "https://ocaml.org/manual/5.2/" + | Oxcaml -> "https://oxcaml.org/documentation/" + in + Some (base_url ^ endpoint) + +(** Drop elements from the head of [list] until [f] returns [true]. *) +let rec drop_until list ~f = + match list with + | [] -> [] + | hd :: rest -> ( + match f hd with + | true -> list + | false -> drop_until rest ~f) + +module Loc_comparison_result = struct + type t = Before | Inside | After + + let is_inside = function + | Before | After -> false + | Inside -> true +end + +let get_jkind_abbrev_doc abbrev = + let open Option.Infix in + let open struct + type docpage = Kind_syntax | Unboxed_types + end in + let* description, docpage = + match abbrev with + | "any" -> + Some + ("The top of the kind lattice; all types have this kind.", Kind_syntax) + | "any_non_null" -> Some ("A synonym for `any mod non_null`.", Kind_syntax) + | "value_or_null" -> + Some + ( "The kind of ordinary OCaml types, but with the possibility that the \ + type contains `null`.", + Kind_syntax ) + | "value" -> Some ("The kind of ordinary OCaml types", Kind_syntax) + | "void" -> + Some + ( "The layout of types that are represented by 0 bits at runtime; \ + these types can contain only 1 value.", + Kind_syntax ) + | "immediate64" -> + Some + ( "On 64-bit platforms, the kind of types inhabited only by tagged \ + integers.", + Kind_syntax ) + | "immediate" -> + Some ("The kind of types inhabited only by tagged integers.", Kind_syntax) + | "immediate_or_null" -> + Some + ( "The kind of types inhabited by tagged integers and the bit pattern \ + containing all 0s.", + Kind_syntax ) + | "float64" -> + Some + ( "The layout of types represented by a 64-bit machine float.", + Unboxed_types ) + | "float32" -> + Some + ( "The layout of types represented by a 32-bit machine float.", + Unboxed_types ) + | "word" -> + Some + ( "The layout of types represented by a native-width machine word.", + Unboxed_types ) + | "bits8" -> + Some + ( "The layout of types represented by an 8-bit machine word.", + Unboxed_types ) + | "bits16" -> + Some + ( "The layout of types represented by a 16-bit machine word.", + Unboxed_types ) + | "bits32" -> + Some + ( "The layout of types represented by a 32-bit machine word.", + Unboxed_types ) + | "bits64" -> + Some + ( "The layout of types represented by a 64-bit machine word.", + Unboxed_types ) + | "vec128" -> + Some + ( "The layout of types represented by a 128-bit machine vector.", + Unboxed_types ) + | "vec256" -> + Some + ( "The layout of types represented by a 256-bit machine vector.", + Unboxed_types ) + | "vec512" -> + Some + ( "The layout of types represented by a 512-bit machine vector.", + Unboxed_types ) + | "immutable_data" -> + Some + ( "The kind of types that contain no mutable parts and no functions.", + Kind_syntax ) + | "sync_data" -> + Some + ( "The kind of types that contain no mutable parts (except possibly \ + for atomic fields) and no functions.", + Kind_syntax ) + | "mutable_data" -> + Some + ( "The kind of types that may have mutable parts but contain no \ + functions.", + Kind_syntax ) + | _ -> None + in + let docpage_str = + match docpage with + | Kind_syntax -> "kinds/syntax/" + | Unboxed_types -> "unboxed-types/intro/" + in + (Some + { name = "Kind abbreviation"; + description; + documentation = syntax_doc_url Oxcaml docpage_str; + level = Advanced + } + : syntax_info) + +let get_mod_bound_doc mod_bound = + let open Option.Infix in + let open struct + type parse_result = + | Axis_pair : 'a Jkind_axis.Axis.t * 'a -> parse_result + | Everything + end in + let* parsed = + match Typemode.Axis_pair.of_string mod_bound with + | exception Not_found -> ( + match mod_bound with + | "everything" -> Some Everything + | __ -> None) + | P (axis, bound) -> Some (Axis_pair (axis, bound)) + in + let* description = + match parsed with + | Axis_pair (Modal (Comonadic _), _) -> + Some + (Format.asprintf + "Values of types of this kind can cross to `%s` from weaker modes." + mod_bound) + | Axis_pair (Modal (Monadic _), _) -> + Some + (Format.asprintf + "Values of types of this kind can cross from `%s` to stronger modes" + mod_bound) + | Axis_pair (Nonmodal Externality, Internal) -> + Some "Values of types of this kind might be pointers to the OCaml heap" + | Axis_pair (Nonmodal Externality, External64) -> + Some + "On 64-bit systems, values of types of this kind are never pointers to \ + the OCaml heap" + | Axis_pair (Nonmodal Externality, External) -> + Some "Values of types of this kind are never pointers to the OCaml heap" + | Axis_pair (Nonmodal Nullability, Maybe_null) -> + Some + "Values of types of this kind might be the bit pattern containing all \ + 0s" + | Axis_pair (Nonmodal Nullability, Non_null) -> + Some + "Values of types of this kind that are also a subkind of `value` are \ + never the bit pattern containing all 0s" + | Axis_pair (Nonmodal Separability, Non_float) -> + Some "Values of types of this kind are never pointers to floats." + | Axis_pair (Nonmodal Separability, Separable) -> + Some + "No type of this kind includes both pointers to a float and other \ + values." + | Axis_pair (Nonmodal Separability, Maybe_separable) -> + Some "Types of this kind may mix pointers to floats with other values." + | Everything -> + Some + "Synonym for \"global aliased many contended portable unyielding \ + immutable stateless external_\", convenient for describing \ + immediates." + in + (Some + { name = "Mod-bound"; + description; + documentation = syntax_doc_url Oxcaml "kinds/intro/"; + level = Advanced + } + : syntax_info) + +module Modal_axis_pair = struct + type t = P : 'a Mode.Value.Axis.t * 'a -> t + + let of_string s = + match Typemode.Axis_pair.of_string s with + | exception Not_found -> None + | P (Modal axis, mode) -> Some (P (axis, mode)) + | P (Nonmodal _, _) -> None +end + +let get_mode_doc mode = + let open Option.Infix in + let* (P (axis, mode)) = Modal_axis_pair.of_string mode in + let* description = + match (axis, mode) with + | Comonadic Areality, Local -> + Some "Values with this mode cannot escape the current region" + | Comonadic Areality, Regional -> None + | Comonadic Areality, Global -> + Some "Values with this mode can escape any region" + | Monadic Contention, Contended -> + Some + "The mutable parts of values with this mode cannot be accessed (unless \ + they are atomic)" + | Monadic Contention, Shared -> + Some + "The mutable parts of values with this mode can be read, but not \ + written (unless they are atomic)" + | Monadic Contention, Uncontended -> + Some "The mutable parts of values with this mode can be fully accessed" + | Comonadic Portability, Nonportable -> + Some + "Values with this mode cannot be sent to other threads, in order to \ + avoid data races." + | Comonadic Portability, Portable -> + Some + "Values with this mode can be sent to other threads without causing \ + data races" + | Monadic Uniqueness, Aliased -> + Some "There may be multiple pointers to values with this mode" + | Monadic Uniqueness, Unique -> + Some + "It is guaranteed that there is only one pointer to values with this \ + mode" + | Comonadic Linearity, Once -> + Some "Functions with this mode can only be called once" + | Comonadic Linearity, Many -> + Some "Functions with this mode can be called any number of times" + | Comonadic Yielding, Yielding -> + Some "Functions with this mode can jump to effect handlers" + | Comonadic Yielding, Unyielding -> + Some "Functions within this value will never jump to an effect handler" + | Monadic Visibility, Immutable -> + Some "The mutable parts of values with this mode cannot be accessed" + | Monadic Visibility, Read -> + Some + "The mutable parts of values with this mode can be read, but not \ + written" + | Monadic Visibility, Read_write -> + Some "The mutable parts of values with this mode can be fully accessed" + | Comonadic Statefulness, Stateful -> + Some "Functions with this mode can read and write mutable data" + | Comonadic Statefulness, Observing -> + Some "Functions with this mode can read but not write mutable data" + | Comonadic Statefulness, Stateless -> + Some "Functions with this mode cannot access mutable data" + in + let doc_url = + let subpage = + match axis with + | Comonadic Areality -> "stack-allocation/intro/" + | Monadic Contention -> "parallelism/01-intro/" + | Comonadic Portability -> "parallelism/01-intro/" + | Monadic Uniqueness -> "uniqueness/intro/" + | Comonadic Linearity -> "uniqueness/intro/" + | Comonadic Yielding -> "modes/intro/" + | Monadic Visibility -> "modes/intro/" + | Comonadic Statefulness -> "modes/intro/" + in + syntax_doc_url Oxcaml subpage + in + (Some + { name = "Mode"; description; documentation = doc_url; level = Advanced } + : syntax_info) + +let get_modality_doc modality = + let open Option.Infix in + let* (P (axis, _)) = Modal_axis_pair.of_string modality in + let description = + (* CR-someday: Detect the context that the modality is within to make this message + more detailed. Ex: "This field is always stronger than _, even if the record has a + weaker mode." *) + match axis with + | Comonadic _ -> + Format.asprintf + "The annotated value's mode is always at least as strong as `%s`, even \ + if its container's mode is weaker." + modality + | Monadic _ -> + Format.asprintf + "The annotated value's mode is always at least as weak as `%s`, even \ + if its container's mode is a stronger." + modality + in + (Some + { name = "Modality"; + description; + documentation = syntax_doc_url Oxcaml "modes/syntax/"; + level = Advanced + } + : syntax_info) + +let get_oxcaml_syntax_doc cursor_loc nodes : syntax_info = + (* Merlin-jst specific: This function gets documentation for oxcaml language + extensions. *) + let compare_cursor_to_loc loc : Loc_comparison_result.t = + match Location_aux.compare_pos cursor_loc loc with + | n when n < 0 -> Before + | n when n > 0 -> After + | _ -> Inside + in + let nodes = List.map nodes ~f:snd in + let nodes = + (* Sometimes the bottom node of [nodes] doesn't include the location of the cursor. + This seems to be because Merlin will find the bottom-most node that contains the + cursor, but then select a child of that node via some heuristics. This is in order + to try to find a node with the environment the user most likely wanted if they, + say, have their cursor on a keyword that isn't represented by a node type in + [Browse_raw.t] (see docstring on [Mtyper.node_at] for more info). But here we + actually want the cursor to be included within all the nodes in [nodes] so that we + can more easily reason about [nodes]. So we drop nodes from the head of [nodes] + until we reach one that includes the cursor. *) + drop_until nodes ~f:(fun node -> + let loc = Browse_raw.node_merlin_loc Location.none node in + match compare_cursor_to_loc loc with + | Inside -> true + | Before | After -> false) + in + let stack_allocation_url = + syntax_doc_url Oxcaml "stack-allocation/reference/" + in + let get_doc_for_attribute (attribute : Parsetree.attribute) : syntax_info = + let builtin_attrs_doc_url = + syntax_doc_url Ocaml "attributes.html#ss:builtin-attributes" + in + (* See below usage of this function for explanation of why this isn't part of the + other big match statement. *) + match attribute with + (* Zero-alloc annotations *) + | { attr_name = { txt = "zero_alloc"; _ }; attr_payload; _ } -> ( + let doc_url = + syntax_doc_url Oxcaml "miscellaneous-extensions/zero_alloc_check/" + in + match attr_payload with + | PStr [] -> + Some + { name = "Zero-alloc annotation"; + description = + "This function does not allocate on the OCaml heap on executions \ + that return normally. The function may allocate if it raises an \ + exception."; + documentation = doc_url; + level = Advanced + } + | PStr + [ { pstr_desc = + Pstr_eval + ( { pexp_desc = + ( Pexp_ident { txt = Lident zero_alloc_flag_name; _ } + | Pexp_apply + ( { pexp_desc = + Pexp_ident + { txt = Lident zero_alloc_flag_name; _ }; + _ + }, + _ ) ); + _ + }, + _ ); + _ + } + ] -> ( + match zero_alloc_flag_name with + | "opt" -> + Some + { name = "Zero-alloc opt annotation"; + description = + "Same as [@zero_alloc], but checks during optimized builds \ + only."; + documentation = doc_url; + level = Advanced + } + | "assume" -> + Some + { name = "Zero-alloc assume annotation"; + description = + "This function is assumed to be zero-alloc, but the compiler \ + does not guarantee it."; + documentation = doc_url; + level = Advanced + } + | "assume_unless_opt" -> + Some + { name = "Zero-alloc assume_unless_opt annotation"; + description = + "Same as [@zero_alloc opt] in optimized builds. Same as \ + [@zero_alloc assume] in non-optimized builds."; + documentation = doc_url; + level = Advanced + } + | "strict" -> + Some + { name = "Zero-alloc strict annotation"; + description = + "This function does not allocate on the OCaml heap (both \ + normal and exceptional returns)."; + documentation = doc_url; + level = Advanced + } + | "arity" -> + Some + { name = "Zero-alloc arity annotation"; + description = + "The function does not allocate when applied to [n] arguments. \ + This can be used to override the arity inferred based on the \ + number of arrows in the type."; + documentation = doc_url; + level = Advanced + } + | _ -> None) + | _ -> + Some + { name = "Unrecognized zero-alloc annotation"; + description = "This is an unrecognized zero-alloc annotation."; + documentation = doc_url; + level = Advanced + }) + | { attr_name = { txt = "noalloc"; _ }; _ } -> + Some + { name = "Noalloc annotation"; + description = + "This external does not allocate, does not raise exceptions, and \ + does not release the domain lock. The compiler will optimize uses \ + to a direct C call."; + documentation = syntax_doc_url Ocaml "intfc.html#ss:c-direct-call"; + level = Advanced + } + (* Inlining annotations *) + | { attr_name = { txt = "inline"; _ }; attr_payload; _ } -> ( + let inline_always_annot : syntax_info = + Some + { name = "Inline always annotation"; + description = + "On a function declaration, causes the function to be inlined at \ + all known call sites (can be overridden by [@inlined]). In \ + addition it will be made available for inlining in other source \ + files (with appropriate build settings permitting .cmx file \ + visibility)"; + documentation = builtin_attrs_doc_url; + level = Advanced + } + in + match attr_payload with + | PStr [] -> inline_always_annot + | PStr + [ { pstr_desc = + Pstr_eval + ( { pexp_desc = Pexp_ident { txt = Lident inline_flag_name; _ }; + _ + }, + _ ); + _ + } + ] -> ( + match inline_flag_name with + | "always" -> inline_always_annot + | "never" -> + Some + { name = "Inline never annotation"; + description = + "This function will not be inlined. In this file (only), this \ + can be overridden at call sites with [@inlined]."; + documentation = builtin_attrs_doc_url; + level = Advanced + } + | "available" -> + Some + { name = "Inline available annotation"; + description = + "Causes the function to be available for inlining in other \ + source files, but does not affect actual inlining decisions. \ + Can be used to ensure cross-source-file inlining even in \ + cases where it would normally be unavailable e.g. a very \ + large function"; + documentation = None; + level = Advanced + } + | _ -> None) + | _ -> + Some + { name = "Unrecognized inline annotation"; + description = "Unrecognized inline annoation"; + documentation = builtin_attrs_doc_url; + level = Advanced + }) + | { attr_name = { txt = "inlined"; _ }; attr_payload; _ } -> ( + let inlined_always_annot : syntax_info = + Some + { name = "Inlined always annotation"; + description = + "If possible, this function call will be inlined. The function \ + must be known to the optimizer (i.e. not an indirect call; and \ + if in another source file, the .cmx for that file must be \ + available and the function available for inlining e.g. by \ + [@inline always] or [@inline available] or the decision of the \ + optimizer). This attribute can override [@inline never] but \ + only within the same source file."; + documentation = builtin_attrs_doc_url; + level = Advanced + } + in + match attr_payload with + | PStr [] -> inlined_always_annot + | PStr + [ { pstr_desc = + Pstr_eval + ( { pexp_desc = Pexp_ident { txt = Lident inline_flag_name; _ }; + _ + }, + _ ); + _ + } + ] -> ( + match inline_flag_name with + | "always" -> inlined_always_annot + | "never" -> + Some + { name = "Inlined never annotation"; + description = + "This function call will not be inlined, overriding any \ + attribute on the function's declaration."; + documentation = builtin_attrs_doc_url; + level = Advanced + } + | "hint" -> + Some + { name = "Inlined hint annotation"; + description = + "If possible, this function call will be inlined, like \ + [@inlined always]. However, no warning is emitted when \ + inlining is not possible."; + documentation = None; + level = Advanced + } + | _ -> None) + | _ -> + Some + { name = "Unrecognized inlined annotation"; + description = "Unrecognized inlined annotation"; + documentation = builtin_attrs_doc_url; + level = Advanced + }) + | { attr_name = { txt = "loop"; _ }; attr_payload; _ } -> ( + let loop_always_desc : syntax_info = + Some + { name = "Loop always annotation"; + description = + "Forces the self-tail-recursive call sites, if any, in the given \ + function to be converted into a loop. If those are the only \ + uses of the recursively-defined function variable, no closure \ + will be generated, and the function can then be inlined as a \ + loop. This transformation is not yet supported for \ + mutually-recursive functions."; + documentation = None; + level = Advanced + } + in + match attr_payload with + | PStr [] -> loop_always_desc + | PStr + [ { pstr_desc = + Pstr_eval + ( { pexp_desc = Pexp_ident { txt = Lident loop_flag_name; _ }; + _ + }, + _ ); + _ + } + ] -> ( + match loop_flag_name with + | "always" -> loop_always_desc + | "never" -> + Some + { name = "Loop never annotation"; + description = + "Prevents the given function from being turned into a loop."; + documentation = None; + level = Advanced + } + | _ -> None) + | _ -> + Some + { name = "Unrecognized loop annotation"; + description = "Unrecognized loop annotation"; + documentation = None; + level = Advanced + }) + | { attr_name = { txt = "unrolled"; _ }; _ } -> + Some + { name = "unrolled annotation"; + description = + "On a recursive function's call site, causes the function body to \ + be unrolled this many times. At present this is not supported if \ + the function was loopified (use [@loop never] to disable). If in \ + another source file, the function must be available for inlining \ + e.g. by [@inline available] with the .cmx file available."; + documentation = builtin_attrs_doc_url; + level = Advanced + } + (* Misc *) + | { attr_name = { txt = "nontail"; _ }; _ } -> + Some + { name = "nontail annotation"; + description = + "This function call will be called normally (with a fresh stack \ + frame), despite appearing in tail position"; + documentation = stack_allocation_url; + level = Advanced + } + | _ -> None + in + match nodes with + (* Modes and modalities *) + | Mode { txt = Mode mode; _ } :: ancestors -> ( + match ancestors with + | Jkind_annotation _ :: _ -> get_mod_bound_doc mode + | _ -> get_mode_doc mode) + | Modality { txt = Modality modality; _ } :: ancestors -> ( + match ancestors with + | Jkind_annotation _ :: _ -> + (* CR-someday: Provide separate documatation for modalities within a jkind *) + get_modality_doc modality + | _ -> get_modality_doc modality) + (* Jkinds *) + | Jkind_annotation { pjkind_desc = Abbreviation abbrev; _ } :: _ -> + get_jkind_abbrev_doc abbrev + | Jkind_annotation { pjkind_desc = Mod _; _ } :: _ -> + Some + { name = "`mod` keyword (in a kind)"; + description = "Types of this kind will cross the following modes"; + documentation = syntax_doc_url Oxcaml "kinds/intro/"; + level = Advanced + } + | Jkind_annotation { pjkind_desc = With (_, with_type, _); _ } :: _ -> ( + match compare_cursor_to_loc with_type.ptyp_loc with + | Before -> + Some + { name = "`with` keyword (in a kind)"; + description = + "Mark a type as structurally included within another; if the \ + with-type does not cross a certain mode, neither does its \ + containing type"; + documentation = syntax_doc_url Oxcaml "kinds/intro/"; + level = Advanced + } + | Inside -> + Some + { name = "with-type"; + description = + "Mark a type as structurally included within another; if the \ + with-type does not cross a certain mode, neither does its \ + containing type"; + documentation = syntax_doc_url Oxcaml "kinds/intro/"; + level = Advanced + } + | After -> + Some + { name = "`@@` keyword (in a kind)"; + description = "Mark a type as included under a modality"; + documentation = syntax_doc_url Oxcaml "kinds/intro/"; + level = Advanced + }) + (* Module Strengthening *) + | Module_type { mty_desc = Tmty_strengthen (_, _, mod_ident); _ } :: _ -> ( + (* Due to a current bug, there is no node for the module name after the `with`, so + it's possible the cursor is on that instead of the `with`. *) + match compare_cursor_to_loc mod_ident.loc with + | Before -> + Some + { name = "Module strengthening"; + description = + "Mark each type in this module type as equal to the corresponding \ + type in the given module"; + documentation = + syntax_doc_url Oxcaml + "miscellaneous-extensions/module-strengthening/"; + level = Advanced + } + | Inside | After -> None) + (* Local allocations *) + | Expression { exp_desc = Texp_exclave _; _ } :: _ -> + Some + { name = "exclave_"; + description = + "End the current region; the following code allocates in the outer \ + region"; + documentation = stack_allocation_url; + level = Advanced + } + | Expression { exp_extra; exp_loc; _ } :: _ + when List.exists exp_extra ~f:(fun (extra, _, _) -> + match extra with + | Typedtree.Texp_stack -> true + | _ -> false) + && (* In this case, [exp_loc] differs from the location returned by + [Browse_raw.node_merlin_loc] (which is whats used to determine [nodes]). + The [Browse_raw.node_merlin_loc] one includes the stack_, whereas [exp_loc] + doesn't. Since we already know that the cursor is in the + [Browse_raw.node_merlin_loc] location (see the usage of [drop_until] + above), we just need to check whether its in [exp_loc] to know whether it's + on the [stack_] keyword. *) + not (Loc_comparison_result.is_inside (compare_cursor_to_loc exp_loc)) + -> + Some + { name = "stack_"; + description = "Force the following allocation to be on stack."; + documentation = stack_allocation_url; + level = Advanced + } + (* Include functor *) + | ( Include_description + { incl_kind = Tincl_functor _ | Tincl_gen_functor _; _ } + | Include_declaration + { incl_kind = Tincl_functor _ | Tincl_gen_functor _; _ } ) + :: _ -> + Some + { name = "include functor"; + description = + "Apply the functor to the current structure up to this point, and \ + include the result in the current structure"; + documentation = + syntax_doc_url Oxcaml "miscellaneous-extensions/include-functor/"; + level = Advanced + } + | nodes -> + (* The locations of attributes nodes only include the attribute name, not the payload. + Additionally, the attribute node is not a parent of the payload node. But the + attribute node will be a sibling of the payload. (Note that the bottom node might + not be the payload but a node within the payload). So here we walk up the list of + ancestors until we find one with an attribute as a child whose location includes + the cursor position, at which point we can conclude the cursor is in the payload. *) + List.find_map_opt nodes ~f:(fun ancestor -> + let children = + Browse_raw.fold_node + (fun _ child acc -> child :: acc) + Env.empty ancestor [] + in + List.find_map_opt children ~f:(fun child -> + match child with + | Attribute attribute -> ( + match compare_cursor_to_loc attribute.attr_loc with + | Inside -> get_doc_for_attribute attribute + | Before | After -> None) + | _ -> None)) let get_syntax_doc cursor_loc node : syntax_info = + let syntax_doc_url = syntax_doc_url Ocaml in match node with | (_, Type_kind _) :: (_, Type_declaration _) @@ -19,7 +779,8 @@ let get_syntax_doc cursor_loc node : syntax_info = type or module from the signature."; documentation = syntax_doc_url - "signaturesubstitution.html#ss:destructive-substitution" + "signaturesubstitution.html#ss:destructive-substitution"; + level = Simple } | (_, Type_kind _) :: (_, Type_declaration _) @@ -32,7 +793,8 @@ let get_syntax_doc cursor_loc node : syntax_info = specification of the signature, and will apply to all the items \ that follow."; documentation = - syntax_doc_url "signaturesubstitution.html#ss:local-substitution" + syntax_doc_url "signaturesubstitution.html#ss:local-substitution"; + level = Simple } | (_, Module_type _) :: (_, Module_type _) @@ -48,7 +810,8 @@ let get_syntax_doc cursor_loc node : syntax_info = abstract module type in a signature into a concrete module type,"; documentation = syntax_doc_url - "signaturesubstitution.html#ss:module-type-substitution" + "signaturesubstitution.html#ss:module-type-substitution"; + level = Simple } | (_, Type_kind Ttype_open) :: (_, Type_declaration { typ_private; _ }) :: _ -> @@ -68,7 +831,12 @@ let get_syntax_doc cursor_loc node : syntax_info = e_description, "extensiblevariants.html#ss:private-extensible" ) in - Some { name; description; documentation = syntax_doc_url url } + Some + { name; + description; + documentation = syntax_doc_url url; + level = Advanced + } | (_, Constructor_declaration _) :: (_, Type_kind (Ttype_variant _)) :: (_, Type_declaration { typ_private; _ }) @@ -94,7 +862,8 @@ let get_syntax_doc cursor_loc node : syntax_info = v_description, "privatetypes.html#ss:private-types-variant" ) in - Some { name; description; documentation = syntax_doc_url url } + Some + { name; description; documentation = syntax_doc_url url; level = Simple } | (_, Core_type _) :: (_, Core_type _) :: (_, Label_declaration _) @@ -115,14 +884,16 @@ let get_syntax_doc cursor_loc node : syntax_info = r_description, "privatetypes.html#ss:private-types-variant" ) in - Some { name; description; documentation = syntax_doc_url url } + Some + { name; description; documentation = syntax_doc_url url; level = Simple } | (_, Type_kind (Ttype_variant _)) :: (_, Type_declaration { typ_private = Public; _ }) :: _ -> Some { name = "Empty Variant Type"; description = "An empty variant type."; - documentation = syntax_doc_url "emptyvariants.html" + documentation = syntax_doc_url "emptyvariants.html"; + level = Advanced } | (_, Type_kind Ttype_abstract) :: (_, Type_declaration { typ_private = Public; typ_manifest = None; _ }) @@ -132,7 +903,8 @@ let get_syntax_doc cursor_loc node : syntax_info = description = "Define variants with arbitrary data structures, including other \ variants, records, and functions"; - documentation = syntax_doc_url "typedecl.html#ss:typedefs" + documentation = syntax_doc_url "typedecl.html#ss:typedefs"; + level = Simple } | (_, Type_kind Ttype_abstract) :: (_, Type_declaration { typ_private = Private; _ }) @@ -143,7 +915,8 @@ let get_syntax_doc cursor_loc node : syntax_info = "Declares a type that is distinct from its implementation type \ `typexpr`."; documentation = - syntax_doc_url "privatetypes.html#ss:private-types-abbrev" + syntax_doc_url "privatetypes.html#ss:private-types-abbrev"; + level = Simple } | (_, Expression _) :: (_, Expression _) @@ -155,7 +928,8 @@ let get_syntax_doc cursor_loc node : syntax_info = description = "Supports a certain class of recursive definitions of non-functional \ values."; - documentation = syntax_doc_url "letrecvalues.html" + documentation = syntax_doc_url "letrecvalues.html"; + level = Simple } | (_, Module_expr _) :: (_, Module_type { mty_desc = Tmty_typeof _; _ }) :: _ -> @@ -164,7 +938,8 @@ let get_syntax_doc cursor_loc node : syntax_info = description = "Expands to the module type (signature or functor type) inferred for \ the module expression `module-expr`. "; - documentation = syntax_doc_url "moduletypeof.html" + documentation = syntax_doc_url "moduletypeof.html"; + level = Simple } | (_, Module_expr _) :: (_, Module_expr _) @@ -176,7 +951,8 @@ let get_syntax_doc cursor_loc node : syntax_info = description = "A simultaneous definition of modules that can refer recursively to \ each others."; - documentation = syntax_doc_url "recursivemodules.html" + documentation = syntax_doc_url "recursivemodules.html"; + level = Simple } | (_, Expression _) :: (_, Expression _) @@ -202,7 +978,8 @@ let get_syntax_doc cursor_loc node : syntax_info = description = "Type constructor which is considered abstract in the scope of the \ sub-expression and replaced by a fresh type variable."; - documentation = syntax_doc_url "locallyabstract.html" + documentation = syntax_doc_url "locallyabstract.html"; + level = Simple } | false -> None) | (_, Module_expr _) @@ -214,6 +991,7 @@ let get_syntax_doc cursor_loc node : syntax_info = description = "Converts a module (structure or functor) to a value of the core \ language that encapsulates the module."; - documentation = syntax_doc_url "firstclassmodules.html" + documentation = syntax_doc_url "firstclassmodules.html"; + level = Simple } - | _ -> None + | _ -> get_oxcaml_syntax_doc cursor_loc node diff --git a/src/analysis/syntax_doc.mli b/src/analysis/syntax_doc.mli index 452806ea8..f6e585435 100644 --- a/src/analysis/syntax_doc.mli +++ b/src/analysis/syntax_doc.mli @@ -1,4 +1,4 @@ val get_syntax_doc : Lexing.position -> (Env.t * Browse_raw.node) list -> - Query_protocol.syntax_doc_result option + Query_protocol.Syntax_doc_result.t option diff --git a/src/commands/query_json.ml b/src/commands/query_json.ml index 4be801090..caee26f8d 100644 --- a/src/commands/query_json.ml +++ b/src/commands/query_json.ml @@ -451,11 +451,16 @@ let json_of_response (type a) (query : a t) (response : a) : json = end | Syntax_document _, resp -> ( match resp with - | `Found info -> + | `Found { name; description; documentation; level } -> `Assoc - [ ("name", `String info.name); - ("description", `String info.description); - ("url", `String info.documentation) + [ ("name", `String name); + ("description", `String description); + ("url", Json.option (fun s -> `String s) documentation); + ( "level", + `String + (match level with + | Simple -> "simple" + | Advanced -> "advanced") ) ] | `No_documentation -> `String "No documentation found") | Expand_ppx _, resp -> diff --git a/src/frontend/query_protocol.ml b/src/frontend/query_protocol.ml index 166d17d3d..10c5b45e0 100644 --- a/src/frontend/query_protocol.ml +++ b/src/frontend/query_protocol.ml @@ -99,8 +99,18 @@ type shape = { shape_loc : Location_aux.t; shape_sub : shape list } type error_filter = { lexing : bool; parsing : bool; typing : bool } -type syntax_doc_result = - { name : string; description : string; documentation : string } +module Syntax_doc_result = struct + module Level = struct + type t = Simple | Advanced + end + + type t = + { name : string; + description : string; + documentation : string option; + level : Level.t + } +end type ppxed_source = { code : string; attr_start : Lexing.position; attr_end : Lexing.position } @@ -219,7 +229,7 @@ type _ t = t | Syntax_document : Msource.position - -> [ `Found of syntax_doc_result | `No_documentation ] t + -> [ `Found of Syntax_doc_result.t | `No_documentation ] t | Expand_ppx : Msource.position -> [ `Found of ppxed_source | `No_ppx ] t | Locate_type : Msource.position diff --git a/src/ocaml/merlin_specific/browse_raw.ml b/src/ocaml/merlin_specific/browse_raw.ml index 257f0cbd5..00c1f6ad6 100644 --- a/src/ocaml/merlin_specific/browse_raw.ml +++ b/src/ocaml/merlin_specific/browse_raw.ml @@ -86,6 +86,10 @@ type node = | Module_binding_name of module_binding | Module_declaration_name of module_declaration | Module_type_declaration_name of module_type_declaration + | Mode of Parsetree.mode Location.loc + | Modality of Parsetree.modality Location.loc + | Jkind_annotation of Parsetree.jkind_annotation + | Attribute of attribute let node_update_env env0 = function | Pattern { pat_env = env } @@ -134,7 +138,11 @@ let node_update_env env0 = function | Include_declaration _ | Open_description _ | Open_declaration _ - | Binding_op _ -> env0 + | Binding_op _ + | Mode _ + | Modality _ + | Jkind_annotation _ + | Attribute _ -> env0 let node_real_loc loc0 = function | Expression { exp_loc = loc } @@ -166,7 +174,11 @@ let node_real_loc loc0 = function | Include_declaration { incl_loc = loc } | Open_description { open_loc = loc } | Open_declaration { open_loc = loc } - | Binding_op { bop_op_name = { loc } } -> loc + | Binding_op { bop_op_name = { loc } } + | Mode { loc } + | Modality { loc } + | Jkind_annotation { pjkind_loc = loc } + | Attribute { attr_name = { loc } } -> loc | Module_type_declaration_name { mtd_name = loc } -> loc.Location.loc | Module_declaration_name { md_name = loc } | Module_binding_name { mb_name = loc } -> loc.Location.loc @@ -285,12 +297,17 @@ let option_fold f' o env (f : _ f0) acc = let of_core_type ct = app (Core_type ct) +let of_jkind_annotation jkind = app (Jkind_annotation jkind) + +let of_jkind_annotation_opt jkind = option_fold of_jkind_annotation jkind + let of_exp_extra (exp, _, _) = match exp with | Texp_constraint ct -> of_core_type ct | Texp_coerce (cto, ct) -> of_core_type ct ** option_fold of_core_type cto | Texp_poly cto -> option_fold of_core_type cto - | Texp_stack | Texp_newtype _ | Texp_mode _ -> id_fold + | Texp_newtype (_, _, jkind, _) -> of_jkind_annotation_opt jkind + | Texp_stack | Texp_mode _ -> id_fold let of_expression e = app (Expression e) ** list_fold of_exp_extra e.exp_extra let of_pat_extra (pat, _, _) = @@ -351,8 +368,9 @@ let of_pattern_desc (type k) (desc : k pattern_desc) = | Tpat_unboxed_tuple ps -> list_fold (fun (_lbl, p, _sort) -> of_pattern p) ps | Tpat_construct (_, _, ps, None) | Tpat_array (_, _, ps) -> list_fold of_pattern ps - | Tpat_construct (_, _, ps, Some (_, ct)) -> + | Tpat_construct (_, _, ps, Some (jkinds, ct)) -> list_fold of_pattern ps ** of_core_type ct + ** list_fold (fun (_, jkind) -> of_jkind_annotation_opt jkind) jkinds | Tpat_record (ls, _) -> list_fold (fun (lid_loc, desc, p) -> @@ -494,7 +512,11 @@ let rec of_expression_desc loc = function let f ?y:(x = 3) () = x ^ *) -and of_function_param fp = of_function_param_kind fp.fp_kind +and of_function_param fp = + of_function_param_kind fp.fp_kind + ** list_fold + (fun (_, _, jkind, _) -> of_jkind_annotation_opt jkind) + fp.fp_newtypes and of_function_param_kind = function | Tparam_pat pat -> of_pattern pat @@ -565,7 +587,7 @@ and of_structure_item_desc = function and of_module_type_desc = function | Tmty_ident _ | Tmty_alias _ -> id_fold - (* CR module strengthening: this might be wrong *) + (* CR module strengthening: need to also fold on the module expression *) | Tmty_strengthen (mty, _, _) -> of_module_type mty | Tmty_signature sg -> app (Signature sg) | Tmty_functor (Named (_, _, mt1), mt2) -> @@ -601,7 +623,9 @@ and of_signature_item_desc = function id_fold and of_core_type_desc = function - | Ttyp_var _ | Ttyp_call_pos | Ttyp_of_kind _ -> id_fold + | Ttyp_var (_, jkind) -> of_jkind_annotation_opt jkind + | Ttyp_call_pos -> id_fold + | Ttyp_of_kind jkind -> of_jkind_annotation jkind | Ttyp_open (_, _, ct) -> of_core_type ct | Ttyp_arrow (_, ct1, ct2) -> of_core_type ct1 ** of_core_type ct2 | Ttyp_tuple cts -> list_fold (fun (_, ty) -> of_core_type ty) cts @@ -614,7 +638,11 @@ and of_core_type_desc = function match of_.of_desc with | OTtag (_, ct) | OTinherit ct -> of_core_type ct) cts - | Ttyp_poly (_, ct) | Ttyp_alias (ct, _, _) -> of_core_type ct + | Ttyp_poly (bindings, ct) -> + list_fold (fun (_, jkind) -> of_jkind_annotation_opt jkind) bindings + ** of_core_type ct + | Ttyp_alias (ct, _, jkind) -> + of_core_type ct ** of_jkind_annotation_opt jkind | Ttyp_variant (rfs, _, _) -> list_fold (fun rf -> app (Row_field rf)) rfs | Ttyp_package pt -> app (Package_type pt) @@ -630,103 +658,153 @@ and of_class_type_field_desc = function | Tctf_constraint (ct1, ct2) -> of_core_type ct1 ** of_core_type ct2 | Tctf_attribute _ -> id_fold -let of_node = function - | Dummy -> id_fold - | Pattern { pat_desc; pat_extra = _ } -> of_pattern_desc pat_desc - | Expression { exp_desc; exp_extra = _; exp_loc } -> - of_expression_desc exp_loc exp_desc - | Case { c_lhs; c_guard; c_rhs } -> - of_pattern c_lhs ** of_expression c_rhs ** option_fold of_expression c_guard - | Class_expr { cl_desc } -> of_class_expr_desc cl_desc - | Class_structure { cstr_self; cstr_fields } -> - of_pattern cstr_self ** list_fold (fun f -> app (Class_field f)) cstr_fields - | Class_field { cf_desc } -> of_class_field_desc cf_desc - | Class_field_kind (Tcfk_virtual ct) -> of_core_type ct - | Class_field_kind (Tcfk_concrete (_, e)) -> of_expression e - | Module_expr { mod_desc } -> of_module_expr_desc mod_desc - | Module_type_constraint Tmodtype_implicit -> id_fold - | Module_type_constraint (Tmodtype_explicit mt) -> of_module_type mt - | Structure { str_items; str_final_env } -> - list_fold_with_next - (fun next item -> - match next with - | None -> app (Structure_item (item, str_final_env)) - | Some item' -> app (Structure_item (item, item'.str_env))) - str_items - | Structure_item ({ str_desc }, _) -> of_structure_item_desc str_desc - | Module_binding mb -> - app (Module_expr mb.mb_expr) ** app (Module_binding_name mb) - | Value_binding { vb_pat; vb_expr } -> - of_pattern vb_pat ** of_expression vb_expr - | Module_type { mty_desc } -> of_module_type_desc mty_desc - | Signature { sig_items; sig_final_env } -> - list_fold_with_next - (fun next item -> - match next with - | None -> app (Signature_item (item, sig_final_env)) - | Some item' -> app (Signature_item (item, item'.sig_env))) - sig_items - | Signature_item ({ sig_desc }, _) -> of_signature_item_desc sig_desc - | Module_declaration md -> - of_module_type md.md_type ** app (Module_declaration_name md) - | Module_type_declaration mtd -> - option_fold of_module_type mtd.mtd_type - ** app (Module_type_declaration_name mtd) - | With_constraint (Twith_type td | Twith_typesubst td) -> - app (Type_declaration td) - | With_constraint (Twith_module _ | Twith_modsubst _) -> id_fold - | With_constraint (Twith_modtype mt | Twith_modtypesubst mt) -> - of_module_type mt - | Core_type { ctyp_desc } -> of_core_type_desc ctyp_desc - | Package_type { pack_fields } -> - list_fold (fun (_, ct) -> of_core_type ct) pack_fields - | Row_field rf -> begin - match rf.rf_desc with - | Ttag (_, _, cts) -> list_fold of_core_type cts - | Tinherit ct -> of_core_type ct - end - | Value_description { val_desc } -> of_core_type val_desc - | Type_declaration { typ_params; typ_cstrs; typ_kind; typ_manifest } -> - let of_typ_cstrs (ct1, ct2, _) = of_core_type ct1 ** of_core_type ct2 in - option_fold of_core_type typ_manifest - ** list_fold of_typ_param typ_params - ** app (Type_kind typ_kind) - ** list_fold of_typ_cstrs typ_cstrs - | Type_kind (Ttype_abstract | Ttype_open) -> id_fold - | Type_kind (Ttype_variant cds) -> - list_fold (fun cd -> app (Constructor_declaration cd)) cds - | Type_kind (Ttype_record lds) | Type_kind (Ttype_record_unboxed_product lds) - -> list_fold of_label_declaration lds - | Type_extension { tyext_params; tyext_constructors } -> - list_fold of_typ_param tyext_params - ** list_fold (fun ec -> app (Extension_constructor ec)) tyext_constructors - | Extension_constructor { ext_kind = Text_decl (_, carg, cto) } -> - option_fold of_core_type cto ** of_constructor_arguments carg - | Extension_constructor { ext_kind = Text_rebind _ } -> id_fold - | Label_declaration { ld_type } -> of_core_type ld_type - | Constructor_declaration { cd_args; cd_res } -> - option_fold of_core_type cd_res ** of_constructor_arguments cd_args - | Class_type { cltyp_desc } -> of_class_type_desc cltyp_desc - | Class_signature { csig_self; csig_fields } -> - of_core_type csig_self - ** list_fold (fun x -> app (Class_type_field x)) csig_fields - | Class_type_field { ctf_desc } -> of_class_type_field_desc ctf_desc - | Class_declaration { ci_params; ci_expr } -> - app (Class_expr ci_expr) ** list_fold of_typ_param ci_params - | Class_description { ci_params; ci_expr } -> - app (Class_type ci_expr) ** list_fold of_typ_param ci_params - | Class_type_declaration { ci_params; ci_expr } -> - app (Class_type ci_expr) ** list_fold of_typ_param ci_params - | Method_call _ -> id_fold - | Record_field _ -> id_fold - | Module_binding_name _ -> id_fold - | Module_declaration_name _ -> id_fold - | Module_type_declaration_name _ -> id_fold - | Open_description _ -> id_fold - | Open_declaration od -> app (Module_expr od.open_expr) - | Include_declaration i -> of_module_expr i.incl_mod - | Include_description i -> of_module_type i.incl_mod - | Binding_op { bop_exp = _ } -> id_fold +let of_mode mode = app (Mode mode) + +let of_modality modality = app (Modality modality) + +let of_jkind_annotation_desc : Parsetree.jkind_annotation_desc -> _ = + let of_core_type (_ : Parsetree.core_type) = + (* CR-someday: Replace [Parsetree.jkind_annotation] with a version where types are + [Typedtree.core_type]s rather than [Parsetree.core_type]s. Then use the proper + [of_core_type] that's defined in this module above. + *) + id_fold + in + function + | Default | Abbreviation _ -> id_fold + | Mod (jkind, modes) -> of_jkind_annotation jkind ** list_fold of_mode modes + | With (jkind, ct, modalities) -> + of_jkind_annotation jkind ** of_core_type ct + ** list_fold of_modality modalities + | Kind_of ct -> of_core_type ct + | Product jkinds -> list_fold of_jkind_annotation jkinds + +let of_attribute (attr : attribute) = + let name = attr.attr_name.txt in + (* There are a number of attributes that start with "merlin." that either modify Merlin + behavior (ex: merlin.loc, merlin.hide, merlin.focus) or deal with the type-checker's + error recovery (ex: merlin.incorrect). Including these attributes in the browse tree + causes Merlin to sometimes choose an incorrect node. These attributes are also + uninteresting - in practice they don't appear in user-written code. *) + match String.is_prefixed name ~by:"merlin." with + | true -> id_fold + | false -> app (Attribute attr) + +let of_node node = + let without_attributes = + match node with + | Dummy -> id_fold + | Pattern { pat_desc; pat_extra = _ } -> of_pattern_desc pat_desc + | Expression { exp_desc; exp_extra = _; exp_loc } -> + of_expression_desc exp_loc exp_desc + | Case { c_lhs; c_guard; c_rhs } -> + of_pattern c_lhs ** of_expression c_rhs + ** option_fold of_expression c_guard + | Class_expr { cl_desc } -> of_class_expr_desc cl_desc + | Class_structure { cstr_self; cstr_fields } -> + of_pattern cstr_self + ** list_fold (fun f -> app (Class_field f)) cstr_fields + | Class_field { cf_desc } -> of_class_field_desc cf_desc + | Class_field_kind (Tcfk_virtual ct) -> of_core_type ct + | Class_field_kind (Tcfk_concrete (_, e)) -> of_expression e + | Module_expr { mod_desc } -> of_module_expr_desc mod_desc + | Module_type_constraint Tmodtype_implicit -> id_fold + | Module_type_constraint (Tmodtype_explicit mt) -> of_module_type mt + | Structure { str_items; str_final_env } -> + list_fold_with_next + (fun next item -> + match next with + | None -> app (Structure_item (item, str_final_env)) + | Some item' -> app (Structure_item (item, item'.str_env))) + str_items + | Structure_item ({ str_desc }, _) -> of_structure_item_desc str_desc + | Module_binding mb -> + app (Module_expr mb.mb_expr) ** app (Module_binding_name mb) + | Value_binding { vb_pat; vb_expr } -> + of_pattern vb_pat ** of_expression vb_expr + | Module_type { mty_desc } -> of_module_type_desc mty_desc + | Signature { sig_items; sig_final_env } -> + list_fold_with_next + (fun next item -> + match next with + | None -> app (Signature_item (item, sig_final_env)) + | Some item' -> app (Signature_item (item, item'.sig_env))) + sig_items + | Signature_item ({ sig_desc }, _) -> of_signature_item_desc sig_desc + | Module_declaration md -> + of_module_type md.md_type ** app (Module_declaration_name md) + | Module_type_declaration mtd -> + option_fold of_module_type mtd.mtd_type + ** app (Module_type_declaration_name mtd) + | With_constraint (Twith_type td | Twith_typesubst td) -> + app (Type_declaration td) + | With_constraint (Twith_module _ | Twith_modsubst _) -> id_fold + | With_constraint (Twith_modtype mt | Twith_modtypesubst mt) -> + of_module_type mt + | Core_type { ctyp_desc } -> of_core_type_desc ctyp_desc + | Package_type { pack_fields } -> + list_fold (fun (_, ct) -> of_core_type ct) pack_fields + | Row_field rf -> begin + match rf.rf_desc with + | Ttag (_, _, cts) -> list_fold of_core_type cts + | Tinherit ct -> of_core_type ct + end + | Value_description { val_desc } -> of_core_type val_desc + | Type_declaration + { typ_params; typ_cstrs; typ_kind; typ_manifest; typ_jkind_annotation } + -> + let of_typ_cstrs (ct1, ct2, _) = of_core_type ct1 ** of_core_type ct2 in + option_fold of_core_type typ_manifest + ** list_fold of_typ_param typ_params + ** app (Type_kind typ_kind) + ** list_fold of_typ_cstrs typ_cstrs + ** of_jkind_annotation_opt typ_jkind_annotation + | Type_kind (Ttype_abstract | Ttype_open) -> id_fold + | Type_kind (Ttype_variant cds) -> + list_fold (fun cd -> app (Constructor_declaration cd)) cds + | Type_kind (Ttype_record lds) + | Type_kind (Ttype_record_unboxed_product lds) -> + list_fold of_label_declaration lds + | Type_extension { tyext_params; tyext_constructors } -> + list_fold of_typ_param tyext_params + ** list_fold (fun ec -> app (Extension_constructor ec)) tyext_constructors + | Extension_constructor { ext_kind = Text_decl (cvars, carg, cto) } -> + option_fold of_core_type cto + ** of_constructor_arguments carg + ** list_fold (fun (_, jkind) -> of_jkind_annotation_opt jkind) cvars + | Extension_constructor { ext_kind = Text_rebind _ } -> id_fold + | Label_declaration { ld_type } -> of_core_type ld_type + | Constructor_declaration { cd_args; cd_res; cd_vars } -> + option_fold of_core_type cd_res + ** of_constructor_arguments cd_args + ** list_fold (fun (_, jkind) -> of_jkind_annotation_opt jkind) cd_vars + | Class_type { cltyp_desc } -> of_class_type_desc cltyp_desc + | Class_signature { csig_self; csig_fields } -> + of_core_type csig_self + ** list_fold (fun x -> app (Class_type_field x)) csig_fields + | Class_type_field { ctf_desc } -> of_class_type_field_desc ctf_desc + | Class_declaration { ci_params; ci_expr } -> + app (Class_expr ci_expr) ** list_fold of_typ_param ci_params + | Class_description { ci_params; ci_expr } -> + app (Class_type ci_expr) ** list_fold of_typ_param ci_params + | Class_type_declaration { ci_params; ci_expr } -> + app (Class_type ci_expr) ** list_fold of_typ_param ci_params + | Method_call _ -> id_fold + | Record_field _ -> id_fold + | Module_binding_name _ -> id_fold + | Module_declaration_name _ -> id_fold + | Module_type_declaration_name _ -> id_fold + | Open_description _ -> id_fold + | Open_declaration od -> app (Module_expr od.open_expr) + | Include_declaration i -> of_module_expr i.incl_mod + | Include_description i -> of_module_type i.incl_mod + | Binding_op { bop_exp = _ } -> id_fold + | Mode _ -> id_fold + | Modality _ -> id_fold + | Jkind_annotation { pjkind_desc } -> of_jkind_annotation_desc pjkind_desc + | Attribute _ -> id_fold + in + without_attributes ** list_fold of_attribute (node_attributes node) let fold_node f env node acc = of_node node env f acc @@ -782,6 +860,10 @@ let string_of_node = function | Open_declaration _ -> "open_declaration" | Include_description _ -> "include_description" | Include_declaration _ -> "include_declaration" + | Mode _ -> "mode" + | Modality _ -> "modality" + | Jkind_annotation _ -> "jkind_annotation" + | Attribute _ -> "attribute" let mkloc = Location.mkloc let reloc txt loc = { loc with Location.txt } diff --git a/src/ocaml/merlin_specific/browse_raw.mli b/src/ocaml/merlin_specific/browse_raw.mli index 1e5c6af19..28565ec52 100644 --- a/src/ocaml/merlin_specific/browse_raw.mli +++ b/src/ocaml/merlin_specific/browse_raw.mli @@ -99,7 +99,17 @@ type node = | Module_binding_name of module_binding | Module_declaration_name of module_declaration | Module_type_declaration_name of module_type_declaration - + | Mode of Parsetree.mode Location.loc + | Modality of Parsetree.modality Location.loc + | Jkind_annotation of Parsetree.jkind_annotation + | Attribute of attribute + (** The location of an [Attribute] is considered to be the location of the + [attr_name], not the overall attribute. This is because in an [Mbrowse.t], an + [Attribute] is not the parent node of its payload. Thus, to ensure that sibling + nodes do not have overlapping locations (otherwise [Mtyper.node_at] would + break), we cannot use the location of the entire attribute. *) + +(** Fold over the children of a node. Note that this is not deep. *) val fold_node : (Env.t -> node -> 'a -> 'a) -> Env.t -> node -> 'a -> 'a (** Accessors for information specific to a node *) diff --git a/src/ocaml/typing/typemode.mli b/src/ocaml/typing/typemode.mli index cd1d8d0e8..a8854ed2a 100644 --- a/src/ocaml/typing/typemode.mli +++ b/src/ocaml/typing/typemode.mli @@ -35,3 +35,10 @@ val untransl_modalities : val transl_mod_bounds : Parsetree.modes -> Types.Jkind_mod_bounds.t val idx_expected_modalities : mut:bool -> Mode.Modality.Value.Const.t + +(* Merlin-only: Expose this so it can be used by syntax_doc.ml *) +module Axis_pair : sig + type t = P : 'a Jkind_axis.Axis.t * 'a -> t + + val of_string : string -> t +end diff --git a/src/utils/std.ml b/src/utils/std.ml index 26637f52e..0c96c8a5f 100644 --- a/src/utils/std.ml +++ b/src/utils/std.ml @@ -344,6 +344,8 @@ module Option = struct let return x = Some x let ( >>= ) x f = bind x ~f let ( >>| ) x f = map x ~f + let ( let* ) opt f = bind opt ~f + let ( let+ ) opt f = map opt ~f end include Infix diff --git a/tests/test-dirs/syntax-document/language-extensions.t/run.t b/tests/test-dirs/syntax-document/language-extensions.t/run.t index 5b2673d3a..414975208 100644 --- a/tests/test-dirs/syntax-document/language-extensions.t/run.t +++ b/tests/test-dirs/syntax-document/language-extensions.t/run.t @@ -273,3 +273,629 @@ on (module S : Set.S with type elt = s) > -filename ./first-class-modules.ml < ./first-class-modules.ml | jq '.value.name' "First class module" + $ call_syntax_doc_and_extract_field () { + > file="$1" + > line="$2" + > col="$3" + > field="$4" + > + > # Print the line, with a ^ underneath pointing at the character + > sed -n "${line}p" "$file" + > printf "%*s^\n" "$col" "" + > + > # Call merlin on the position + > "$MERLIN" single syntax-document -position "$line:$col" -filename "$file" < "$file" \ + > | jq "if (.value | type) == \"string\" then .value else .value.$field end" -r + > } + + $ syn_doc_name () { + > call_syntax_doc_and_extract_field "$1" "$2" "$3" name + > } + + $ syn_doc_desc () { + > call_syntax_doc_and_extract_field "$1" "$2" "$3" description + > } + +Convenience function to ensure we haven't made any syntax errors. +(This is especially convenient for oxcaml language features, as their syntax is volatile.) + + $ syntax_errors () { + > "$MERLIN" single errors -filename "$1" < "$1" | jq '.value[] | select(.type == "parser")' + > } + +// Modes +# CR-someday: Add raw modes (and @?) to typedtree so this information can be recovered + + $ cat > modes.ml << EOF + > module type S = sig + > type t = foo @ local -> bar @ portable + > end + > let (f @ stateless) (x : int @ contended) = (_ : _ @ contended) + > let x : int @ local = 10 + > EOF + + $ syntax_errors modes.ml + + $ syn_doc_name modes.ml 2 15 + type t = foo @ local -> bar @ portable + ^ + No documentation found + + $ syn_doc_name modes.ml 2 19 + type t = foo @ local -> bar @ portable + ^ + No documentation found + + $ syn_doc_name modes.ml 2 30 + type t = foo @ local -> bar @ portable + ^ + No documentation found + + $ syn_doc_name modes.ml 2 35 + type t = foo @ local -> bar @ portable + ^ + No documentation found + + $ syn_doc_name modes.ml 4 7 + let (f @ stateless) (x : int @ contended) = (_ : _ @ contended) + ^ + No documentation found + + $ syn_doc_name modes.ml 4 12 + let (f @ stateless) (x : int @ contended) = (_ : _ @ contended) + ^ + No documentation found + + $ syn_doc_name modes.ml 4 29 + let (f @ stateless) (x : int @ contended) = (_ : _ @ contended) + ^ + No documentation found + + $ syn_doc_name modes.ml 4 33 + let (f @ stateless) (x : int @ contended) = (_ : _ @ contended) + ^ + No documentation found + + $ syn_doc_name modes.ml 4 51 + let (f @ stateless) (x : int @ contended) = (_ : _ @ contended) + ^ + No documentation found + + $ syn_doc_name modes.ml 4 58 + let (f @ stateless) (x : int @ contended) = (_ : _ @ contended) + ^ + No documentation found + + $ syn_doc_name modes.ml 5 12 + let x : int @ local = 10 + ^ + No documentation found + + $ syn_doc_name modes.ml 5 17 + let x : int @ local = 10 + ^ + No documentation found + + $ syn_doc_desc modes.ml 5 17 + let x : int @ local = 10 + ^ + No documentation found + +// Modalities +# CR-someday: Add raw modalities (and @@?) to typedtree so this information can be recovered + + $ cat > modalities.ml << EOF + > module type S = sig @@ portable + > val foo : int -> int @@ stateless + > end + > external id : 'a -> 'a @@ portable = "%identity" + > type t = { foo : int @@ contended } + > EOF + + $ syntax_errors modalities.ml + + $ syn_doc_name modalities.ml 1 21 + module type S = sig @@ portable + ^ + No documentation found + + $ syn_doc_name modalities.ml 1 23 + module type S = sig @@ portable + ^ + No documentation found + + $ syn_doc_name modalities.ml 2 23 + val foo : int -> int @@ stateless + ^ + No documentation found + + $ syn_doc_name modalities.ml 2 28 + val foo : int -> int @@ stateless + ^ + No documentation found + + $ syn_doc_name modalities.ml 4 24 + external id : 'a -> 'a @@ portable = "%identity" + ^ + No documentation found + + $ syn_doc_name modalities.ml 4 28 + external id : 'a -> 'a @@ portable = "%identity" + ^ + No documentation found + + $ syn_doc_name modalities.ml 5 22 + type t = { foo : int @@ contended } + ^ + Record Type + + $ syn_doc_name modalities.ml 5 28 + type t = { foo : int @@ contended } + ^ + Record Type + +# CR-someday: Since modalities aren't yet supported, this is falling through to the record +# case + $ syn_doc_desc modalities.ml 5 28 + type t = { foo : int @@ contended } + ^ + Defines variants with a fixed set of fields + +// Kinds + + $ cat > kinds.ml << EOF + > type ('a : immediate) t : value mod portable with 'a @@ global + > module type S = sig + > val id : ('a : value). 'a -> 'a + > val id2 : ('a : value) -> ('a : value) + > end + > let f (x : (_ : value)) = (x : (_ : value)) + > type t : float64 mod everything + > EOF + + $ syntax_errors kinds.ml + + $ syn_doc_name kinds.ml 1 16 + type ('a : immediate) t : value mod portable with 'a @@ global + ^ + Kind abbreviation + + $ syn_doc_name kinds.ml 1 28 + type ('a : immediate) t : value mod portable with 'a @@ global + ^ + Kind abbreviation + + $ syn_doc_name kinds.ml 1 33 + type ('a : immediate) t : value mod portable with 'a @@ global + ^ + `mod` keyword (in a kind) + + $ syn_doc_name kinds.ml 1 40 + type ('a : immediate) t : value mod portable with 'a @@ global + ^ + Mod-bound + + $ syn_doc_name kinds.ml 1 47 + type ('a : immediate) t : value mod portable with 'a @@ global + ^ + `with` keyword (in a kind) + + $ syn_doc_name kinds.ml 1 51 + type ('a : immediate) t : value mod portable with 'a @@ global + ^ + with-type + + $ syn_doc_name kinds.ml 1 53 + type ('a : immediate) t : value mod portable with 'a @@ global + ^ + `@@` keyword (in a kind) + + $ syn_doc_name kinds.ml 1 57 + type ('a : immediate) t : value mod portable with 'a @@ global + ^ + Modality + + $ syn_doc_name kinds.ml 3 20 + val id : ('a : value). 'a -> 'a + ^ + Kind abbreviation + + $ syn_doc_name kinds.ml 4 20 + val id2 : ('a : value) -> ('a : value) + ^ + Kind abbreviation + + $ syn_doc_name kinds.ml 4 37 + val id2 : ('a : value) -> ('a : value) + ^ + Kind abbreviation + + $ syn_doc_name kinds.ml 6 19 + let f (x : (_ : value)) = (x : (_ : value)) + ^ + Kind abbreviation + + $ syn_doc_name kinds.ml 6 36 + let f (x : (_ : value)) = (x : (_ : value)) + ^ + Kind abbreviation + + $ syn_doc_name kinds.ml 7 13 + type t : float64 mod everything + ^ + Kind abbreviation + + $ syn_doc_name kinds.ml 7 28 + type t : float64 mod everything + ^ + Mod-bound + + $ syn_doc_desc kinds.ml 7 13 + type t : float64 mod everything + ^ + The layout of types represented by a 64-bit machine float. + + $ syn_doc_desc kinds.ml 7 28 + type t : float64 mod everything + ^ + Synonym for "global aliased many contended portable unyielding immutable stateless external_", convenient for describing immediates. + + $ syn_doc_desc kinds.ml 1 40 + type ('a : immediate) t : value mod portable with 'a @@ global + ^ + Values of types of this kind can cross to `portable` from weaker modes. + +// include functor + + $ cat > include_functor.ml << EOF + > module type F = functor (S : sig end) -> sig end + > module F (S : sig end) = struct end + > module M : sig + > include functor F + > end = struct + > include functor F + > end + > EOF + + $ syntax_errors include_functor.ml + + $ syn_doc_name include_functor.ml 4 2 + include functor F + ^ + include functor + + $ syn_doc_name include_functor.ml 4 13 + include functor F + ^ + include functor + + $ syn_doc_name include_functor.ml 4 18 + include functor F + ^ + No documentation found + + $ syn_doc_name include_functor.ml 6 2 + include functor F + ^ + include functor + + $ syn_doc_name include_functor.ml 6 13 + include functor F + ^ + include functor + + $ syn_doc_name include_functor.ml 6 18 + include functor F + ^ + No documentation found + +// local allocations + + $ cat > local.ml << EOF + > let f x = + > let _ = stack_ Some x in + > exclave_ Some x + > let f x = g x [@nontail] + > EOF + + $ syntax_errors local.ml + + $ syn_doc_name local.ml 2 13 + let _ = stack_ Some x in + ^ + stack_ + + $ syn_doc_name local.ml 2 18 + let _ = stack_ Some x in + ^ + No documentation found + + $ syn_doc_name local.ml 3 4 + exclave_ Some x + ^ + exclave_ + + $ syn_doc_name local.ml 3 13 + exclave_ Some x + ^ + No documentation found + + $ syn_doc_name local.ml 4 20 + let f x = g x [@nontail] + ^ + nontail annotation + +// zero-alloc annotations + + $ cat > zero_alloc.ml << EOF + > let[@zero_alloc] f x = x + > let[@zero_alloc opt] f x = x + > let[@zero_alloc assume] f x = x + > let[@zero_alloc strict] f x = x + > let f x = + > (g[@zero_alloc assume]) x + > module type S = sig + > val[@zero_alloc] f : int -> int + > val[@zero_alloc arity 1] f : t + > end + > external id : 'a -> 'a = "%identity" [@@noalloc] + > let[@zero_alloc assume_unless_opt] f x = x + > EOF + + $ syntax_errors zero_alloc.ml + + $ syn_doc_name zero_alloc.ml 1 10 + let[@zero_alloc] f x = x + ^ + Zero-alloc annotation + + $ syn_doc_name zero_alloc.ml 2 10 + let[@zero_alloc opt] f x = x + ^ + Zero-alloc opt annotation + + $ syn_doc_name zero_alloc.ml 2 18 + let[@zero_alloc opt] f x = x + ^ + Zero-alloc opt annotation + + $ syn_doc_name zero_alloc.ml 3 10 + let[@zero_alloc assume] f x = x + ^ + Zero-alloc assume annotation + + $ syn_doc_name zero_alloc.ml 3 18 + let[@zero_alloc assume] f x = x + ^ + Zero-alloc assume annotation + + $ syn_doc_name zero_alloc.ml 4 10 + let[@zero_alloc strict] f x = x + ^ + Zero-alloc strict annotation + + $ syn_doc_name zero_alloc.ml 4 18 + let[@zero_alloc strict] f x = x + ^ + Zero-alloc strict annotation + + $ syn_doc_name zero_alloc.ml 6 3 + (g[@zero_alloc assume]) x + ^ + No documentation found + + $ syn_doc_name zero_alloc.ml 6 10 + (g[@zero_alloc assume]) x + ^ + Zero-alloc assume annotation + + $ syn_doc_name zero_alloc.ml 6 18 + (g[@zero_alloc assume]) x + ^ + Zero-alloc assume annotation + + $ syn_doc_name zero_alloc.ml 8 10 + val[@zero_alloc] f : int -> int + ^ + Zero-alloc annotation + + $ syn_doc_name zero_alloc.ml 9 13 + val[@zero_alloc arity 1] f : t + ^ + Zero-alloc arity annotation + + $ syn_doc_name zero_alloc.ml 9 21 + val[@zero_alloc arity 1] f : t + ^ + Zero-alloc arity annotation + + $ syn_doc_name zero_alloc.ml 9 24 + val[@zero_alloc arity 1] f : t + ^ + Zero-alloc arity annotation + + $ syn_doc_name zero_alloc.ml 11 44 + external id : 'a -> 'a = "%identity" [@@noalloc] + ^ + Noalloc annotation + $ syn_doc_name zero_alloc.ml 12 10 + let[@zero_alloc assume_unless_opt] f x = x + ^ + Zero-alloc assume_unless_opt annotation + + $ syn_doc_name zero_alloc.ml 12 18 + let[@zero_alloc assume_unless_opt] f x = x + ^ + Zero-alloc assume_unless_opt annotation + +// inlining annotations + + $ cat > inlining.ml << EOF + > let[@inline always] f x = x + > let[@inline never] f x = x + > let[@inline available] f x = x + > let[@inline] f x = x + > let () = (f [@inlined always]) 0 + > let () = (f [@inlined never]) 0 + > let () = (f [@inlined hint]) 0 + > let () = (f [@inlined]) 0 + > let () = (f [@loop always]) 0 + > let () = (f [@loop never]) 0 + > let () = (f [@loop]) 0 + > let () = (f [@unrolled 10]) 0 + > EOF + + $ syntax_errors inlining.ml + + $ syn_doc_name inlining.ml 1 10 + let[@inline always] f x = x + ^ + Inline always annotation + + $ syn_doc_name inlining.ml 1 15 + let[@inline always] f x = x + ^ + Inline always annotation + + $ syn_doc_name inlining.ml 2 10 + let[@inline never] f x = x + ^ + Inline never annotation + + $ syn_doc_name inlining.ml 2 13 + let[@inline never] f x = x + ^ + Inline never annotation + + $ syn_doc_name inlining.ml 3 10 + let[@inline available] f x = x + ^ + Inline available annotation + + $ syn_doc_name inlining.ml 3 13 + let[@inline available] f x = x + ^ + Inline available annotation + + $ syn_doc_name inlining.ml 4 10 + let[@inline] f x = x + ^ + Inline always annotation + + $ syn_doc_name inlining.ml 5 17 + let () = (f [@inlined always]) 0 + ^ + Inlined always annotation + + $ syn_doc_name inlining.ml 5 25 + let () = (f [@inlined always]) 0 + ^ + Inlined always annotation + + $ syn_doc_name inlining.ml 6 17 + let () = (f [@inlined never]) 0 + ^ + Inlined never annotation + + $ syn_doc_name inlining.ml 6 25 + let () = (f [@inlined never]) 0 + ^ + Inlined never annotation + + $ syn_doc_name inlining.ml 7 17 + let () = (f [@inlined hint]) 0 + ^ + Inlined hint annotation + + $ syn_doc_name inlining.ml 7 25 + let () = (f [@inlined hint]) 0 + ^ + Inlined hint annotation + + $ syn_doc_name inlining.ml 8 17 + let () = (f [@inlined]) 0 + ^ + Inlined always annotation + + $ syn_doc_name inlining.ml 9 17 + let () = (f [@loop always]) 0 + ^ + Loop always annotation + + $ syn_doc_name inlining.ml 9 22 + let () = (f [@loop always]) 0 + ^ + Loop always annotation + + $ syn_doc_name inlining.ml 10 17 + let () = (f [@loop never]) 0 + ^ + Loop never annotation + + $ syn_doc_name inlining.ml 10 22 + let () = (f [@loop never]) 0 + ^ + Loop never annotation + + $ syn_doc_name inlining.ml 11 17 + let () = (f [@loop]) 0 + ^ + Loop always annotation + + $ syn_doc_name inlining.ml 12 18 + let () = (f [@unrolled 10]) 0 + ^ + unrolled annotation + + $ syn_doc_name inlining.ml 12 24 + let () = (f [@unrolled 10]) 0 + ^ + unrolled annotation + +// module strengthening + + $ cat > module_strengthening.ml << EOF + > module type S = sig end + > module M = struct end + > module type S = S with M + > EOF + + $ syntax_errors module_strengthening.ml + + $ syn_doc_name module_strengthening.ml 3 16 + module type S = S with M + ^ + No documentation found + + $ syn_doc_name module_strengthening.ml 3 20 + module type S = S with M + ^ + Module strengthening + + $ syn_doc_name module_strengthening.ml 3 23 + module type S = S with M + ^ + No documentation found + +Validate that docstrings, URLs, and levels are being created correctly + + $ cat > validate.ml << EOF + > let rec name1 = 1 :: name2 and name2 = 2 :: name1 + > type t : value + > EOF + + $ $MERLIN single syntax-document -position 1:6 -filename validate.ml < validate.ml | jq .value + { + "name": "Recursive value definition", + "description": "Supports a certain class of recursive definitions of non-functional values.", + "url": "https://ocaml.org/manual/5.2/letrecvalues.html", + "level": "simple" + } + + $ $MERLIN single syntax-document -position 2:11 -filename validate.ml < validate.ml | jq .value + { + "name": "Kind abbreviation", + "description": "The kind of ordinary OCaml types", + "url": "https://oxcaml.org/documentation/kinds/syntax/", + "level": "advanced" + }