jon.recoil.org

Source file overrides.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
open Std

let { Logger.log } = Logger.for_section "overrides"

let error_failed_to_parse_position_field_values =
  Error "failed to parse position field values"

let error_unexpected_position_expression_structure =
  Error "unexpected position expression structure"

let error_unexpected_payload_expression_structure =
  Error "unexpected payload expression structure"

let error_unexpected_merlin_override_attribute_structure =
  Error "unexpected merlin.X attribute structure"

module Attribute_name = struct
  type 'a t = Document : string t | Locate : Lexing.position t

  let to_name (type a) (t : a t) =
    match t with
    | Document -> "merlin.document"
    | Locate -> "merlin.locate"
end

module Override = struct
  let expr_to_pos ({ pexp_desc; _ } : Parsetree.expression) =
    match pexp_desc with
    | Pexp_record
        ( [ ( { txt = Lident "pos_fname"; _ },
              { pexp_desc = Pexp_constant (Pconst_string (pos_fname, _, _)); _ }
            );
            ( { txt = Lident "pos_lnum"; _ },
              { pexp_desc = Pexp_constant (Pconst_integer (lnum, None)); _ } );
            ( { txt = Lident "pos_bol"; _ },
              { pexp_desc = Pexp_constant (Pconst_integer (bol, None)); _ } );
            ( { txt = Lident "pos_cnum"; _ },
              { pexp_desc = Pexp_constant (Pconst_integer (cnum, None)); _ } )
          ],
          None ) -> (
      match
        (int_of_string_opt lnum, int_of_string_opt bol, int_of_string_opt cnum)
      with
      | Some pos_lnum, Some pos_bol, Some pos_cnum ->
        Ok { Lexing.pos_fname; pos_lnum; pos_bol; pos_cnum }
      | _ -> error_failed_to_parse_position_field_values)
    | _ -> error_unexpected_position_expression_structure

  module Payload = struct
    type 'a t =
      | Document : string -> string t
      | Locate : Lexing.position -> Lexing.position t

    let of_expression (type a) ~(attribute_name : a Attribute_name.t)
        (expr : Parsetree.expression) : (a t, string) result =
      match (attribute_name, expr.pexp_desc) with
      | Document, Pexp_constant (Pconst_string (documentation, _, _)) ->
        Ok (Document documentation)
      | Locate, Pexp_record _ ->
        Result.map (expr_to_pos expr) ~f:(fun pos -> Locate pos)
      | _ -> error_unexpected_payload_expression_structure
  end

  type 'a t = { loc : Location.t; payload : 'a Payload.t }

  let of_expression ~attribute_name ({ pexp_desc; _ } : Parsetree.expression) =
    match pexp_desc with
    | Pexp_record
        ( [ ( { txt = Lident "location"; _ },
              { pexp_desc =
                  Pexp_record
                    ( [ ({ txt = Lident "loc_start"; _ }, loc_start_expr);
                        ({ txt = Lident "loc_end"; _ }, loc_end_expr);
                        ({ txt = Lident "loc_ghost"; _ }, loc_ghost_expr)
                      ],
                      None );
                _
              } );
            ({ txt = Lident "payload"; _ }, payload_expression)
          ],
          None ) ->
      let open Misc.Stdlib.Monad.Result.Syntax in
      let* loc_start = expr_to_pos loc_start_expr in
      let* loc_end = expr_to_pos loc_end_expr in
      let* loc_ghost =
        match loc_ghost_expr.pexp_desc with
        | Pexp_construct ({ txt = Lident "false"; _ }, None) -> Ok false
        | Pexp_construct ({ txt = Lident "true"; _ }, None) -> Ok true
        | _ -> error_failed_to_parse_position_field_values
      in
      let* payload = Payload.of_expression ~attribute_name payload_expression in
      Ok { loc = { Location.loc_start; loc_end; loc_ghost }; payload }
    | _ -> error_unexpected_merlin_override_attribute_structure

  let payload (type a) (t : a t) : a =
    match t.payload with
    | Document doc -> doc
    | Locate loc -> loc

  let to_interval t =
    Overrides_interval_tree.Interval.create ~loc:t.loc ~payload:t
end

type 'a t = 'a Override.t Overrides_interval_tree.t

let rec of_payload ~attribute_name ({ pexp_desc; _ } : Parsetree.expression) =
  match pexp_desc with
  | Pexp_construct
      ( { txt = Lident "::"; _ },
        Some { pexp_desc = Pexp_tuple [ (None, override); (None, rest) ]; _ } )
    -> (
    match Override.of_expression ~attribute_name override with
    | Ok override -> override :: of_payload ~attribute_name rest
    | Error err ->
      log ~title:"of_payload" "%s" err;
      of_payload ~attribute_name rest)
  | _ -> []

let of_attribute ~attribute_name (attribute : Parsetree.attribute) =
  match attribute with
  | { attr_payload = PStr [ { pstr_desc = Pstr_eval (expr, _); _ } ]; _ } ->
    Ok (of_payload ~attribute_name expr)
  | _ -> error_unexpected_merlin_override_attribute_structure

let get_overrides ~attribute_name (ppx_parsetree : Mreader.parsetree) =
  let attributes =
    match ppx_parsetree with
    | `Interface signature ->
      List.filter_map signature.psg_items
        ~f:(fun (signature_item : Parsetree.signature_item) ->
          match signature_item.psig_desc with
          | Psig_attribute ({ attr_name = { txt; _ }; _ } as attr)
            when String.equal (Attribute_name.to_name attribute_name) txt ->
            Some attr
          | _ -> None)
    | `Implementation structure ->
      List.filter_map structure
        ~f:(fun (structure_item : Parsetree.structure_item) ->
          match structure_item.pstr_desc with
          | Pstr_attribute ({ attr_name = { txt; _ }; _ } as attr)
            when String.equal (Attribute_name.to_name attribute_name) txt ->
            Some attr
          | _ -> None)
  in
  attributes
  |> List.concat_map ~f:(fun attribute ->
      match of_attribute ~attribute_name attribute with
      | Ok overrides -> overrides
      | Error err ->
        log ~title:"get_overrides" "%s" err;
        [])
  |> List.filter_map ~f:(fun (override : _ Override.t) ->
      match Override.to_interval override with
      | Ok interval -> Some interval
      | Error err ->
        log ~title:"get_overrides" "%s" err;
        None)
  |> Overrides_interval_tree.of_alist

let find t ~cursor = Overrides_interval_tree.find t cursor