Skip to content

Commit 5e6b3c9

Browse files
authored
fix: merlin document safety (#890)
only use merlin features on merlin sources. enforce this with the type system Signed-off-by: Rudi Grinberg <[email protected]>
1 parent bd0c48f commit 5e6b3c9

24 files changed

+708
-595
lines changed

ocaml-lsp-server/src/code_actions/action_add_rec.ml

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -65,25 +65,28 @@ let code_action_add_rec uri diagnostics doc loc =
6565
()
6666

6767
let code_action doc (params : CodeActionParams.t) =
68-
let pos_start = Position.logical params.range.start in
69-
let m_diagnostic =
70-
List.find params.context.diagnostics ~f:(fun d ->
71-
let is_unbound () =
72-
String.is_prefix d.Diagnostic.message ~prefix:"Unbound value"
73-
and in_range () =
74-
match Position.compare_inclusion params.range.start d.range with
75-
| `Outside _ -> false
76-
| `Inside -> true
77-
in
78-
in_range () && is_unbound ())
79-
in
80-
match m_diagnostic with
81-
| None -> Fiber.return None
82-
| Some d ->
83-
let+ loc =
84-
Document.with_pipeline_exn doc (fun pipeline ->
85-
has_missing_rec pipeline pos_start)
68+
match Document.kind doc with
69+
| `Other -> Fiber.return None
70+
| `Merlin merlin -> (
71+
let pos_start = Position.logical params.range.start in
72+
let m_diagnostic =
73+
List.find params.context.diagnostics ~f:(fun d ->
74+
let is_unbound () =
75+
String.is_prefix d.Diagnostic.message ~prefix:"Unbound value"
76+
and in_range () =
77+
match Position.compare_inclusion params.range.start d.range with
78+
| `Outside _ -> false
79+
| `Inside -> true
80+
in
81+
in_range () && is_unbound ())
8682
in
87-
Option.map loc ~f:(code_action_add_rec params.textDocument.uri [ d ] doc)
83+
match m_diagnostic with
84+
| None -> Fiber.return None
85+
| Some d ->
86+
let+ loc =
87+
Document.Merlin.with_pipeline_exn merlin (fun pipeline ->
88+
has_missing_rec pipeline pos_start)
89+
in
90+
Option.map loc ~f:(code_action_add_rec params.textDocument.uri [ d ] doc))
8891

8992
let t = { Code_action.kind = QuickFix; run = code_action }

ocaml-lsp-server/src/code_actions/action_construct.ml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ let action_kind = "construct"
55

66
let code_action doc (params : CodeActionParams.t) =
77
match Document.kind doc with
8-
| `Other | `Merlin Intf -> Fiber.return None
9-
| `Merlin Impl ->
8+
| `Other -> Fiber.return None
9+
| `Merlin m when Document.Merlin.kind m = Intf -> Fiber.return None
10+
| `Merlin merlin ->
1011
let pos = Position.logical params.range.Range.end_ in
1112
(* we want this predicate to quickly eliminate prefixes that don't fit to be
1213
a hole *)
@@ -17,7 +18,7 @@ let code_action doc (params : CodeActionParams.t) =
1718
if not (Typed_hole.can_be_hole prefix) then Fiber.return None
1819
else
1920
let+ structures =
20-
Document.with_pipeline_exn doc (fun pipeline ->
21+
Document.Merlin.with_pipeline_exn merlin (fun pipeline ->
2122
let typedtree =
2223
let typer = Mpipeline.typer_result pipeline in
2324
Mtyper.get_typedtree typer

ocaml-lsp-server/src/code_actions/action_destruct.ml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,15 @@ let code_action_of_case_analysis ~supportsJumpToNextHole doc uri (loc, newText)
4040
let code_action (state : State.t) doc (params : CodeActionParams.t) =
4141
let uri = params.textDocument.uri in
4242
match Document.kind doc with
43-
| `Other | `Merlin Intf -> Fiber.return None
44-
| `Merlin Impl -> (
43+
| `Other -> Fiber.return None
44+
| `Merlin m when Document.Merlin.kind m = Intf -> Fiber.return None
45+
| `Merlin merlin -> (
4546
let command =
4647
let start = Position.logical params.range.start in
4748
let finish = Position.logical params.range.end_ in
4849
Query_protocol.Case_analysis (start, finish)
4950
in
50-
let* res = Document.dispatch doc command in
51+
let* res = Document.Merlin.dispatch merlin command in
5152
match res with
5253
| Ok (loc, newText) ->
5354
let+ newText =

ocaml-lsp-server/src/code_actions/action_inferred_intf.ml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ let code_action_of_intf doc intf range =
2626

2727
let code_action (state : State.t) doc (params : CodeActionParams.t) =
2828
match Document.kind doc with
29-
| `Other | `Merlin Impl -> Fiber.return None
30-
| `Merlin Intf -> (
29+
| `Other -> Fiber.return None
30+
| `Merlin m when Document.Merlin.kind m = Impl -> Fiber.return None
31+
| `Merlin _ -> (
3132
let* intf = Inference.infer_intf state doc in
3233
match intf with
3334
| None -> Fiber.return None

ocaml-lsp-server/src/code_actions/action_inline.ml

Lines changed: 49 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -403,53 +403,58 @@ let inline_edits pipeline task =
403403

404404
let code_action doc (params : CodeActionParams.t) =
405405
let open Option.O in
406-
Document.with_pipeline_exn doc (fun pipeline ->
407-
let* typedtree =
408-
match Mtyper.get_typedtree (Mpipeline.typer_result pipeline) with
409-
| `Interface _ -> None
410-
| `Implementation x -> Some x
411-
in
412-
let* task = find_inline_task typedtree params.range.start in
413-
inline_edits pipeline task)
414-
|> Fiber.map ~f:(fun m_edits ->
415-
let* edits, m_error = m_edits in
416-
match (edits, m_error) with
417-
| [], None -> None
418-
| [], Some error ->
419-
let action =
420-
CodeAction.create
421-
~title:action_title
422-
~kind:CodeActionKind.RefactorInline
423-
~isPreferred:false
424-
~disabled:
425-
(CodeAction.create_disabled ~reason:(string_of_error error))
426-
()
427-
in
428-
Some action
429-
| _ :: _, (Some _ | None) ->
430-
let edit =
431-
let version = Document.version doc in
432-
let textDocument =
433-
OptionalVersionedTextDocumentIdentifier.create
434-
~uri:params.textDocument.uri
435-
~version
406+
match Document.kind doc with
407+
| `Other -> Fiber.return None
408+
| `Merlin merlin ->
409+
Document.Merlin.with_pipeline_exn merlin (fun pipeline ->
410+
let* typedtree =
411+
match Mtyper.get_typedtree (Mpipeline.typer_result pipeline) with
412+
| `Interface _ -> None
413+
| `Implementation x -> Some x
414+
in
415+
let* task = find_inline_task typedtree params.range.start in
416+
inline_edits pipeline task)
417+
|> Fiber.map ~f:(fun m_edits ->
418+
let* edits, m_error = m_edits in
419+
match (edits, m_error) with
420+
| [], None -> None
421+
| [], Some error ->
422+
let action =
423+
CodeAction.create
424+
~title:action_title
425+
~kind:CodeActionKind.RefactorInline
426+
~isPreferred:false
427+
~disabled:
428+
(CodeAction.create_disabled ~reason:(string_of_error error))
436429
()
437430
in
431+
Some action
432+
| _ :: _, (Some _ | None) ->
438433
let edit =
439-
TextDocumentEdit.create
440-
~textDocument
441-
~edits:(List.map edits ~f:(fun e -> `TextEdit e))
434+
let version = Document.version doc in
435+
let textDocument =
436+
OptionalVersionedTextDocumentIdentifier.create
437+
~uri:params.textDocument.uri
438+
~version
439+
()
440+
in
441+
let edit =
442+
TextDocumentEdit.create
443+
~textDocument
444+
~edits:(List.map edits ~f:(fun e -> `TextEdit e))
445+
in
446+
WorkspaceEdit.create
447+
~documentChanges:[ `TextDocumentEdit edit ]
448+
()
449+
in
450+
let action =
451+
CodeAction.create
452+
~title:action_title
453+
~kind:CodeActionKind.RefactorInline
454+
~edit
455+
~isPreferred:false
456+
()
442457
in
443-
WorkspaceEdit.create ~documentChanges:[ `TextDocumentEdit edit ] ()
444-
in
445-
let action =
446-
CodeAction.create
447-
~title:action_title
448-
~kind:CodeActionKind.RefactorInline
449-
~edit
450-
~isPreferred:false
451-
()
452-
in
453-
Some action)
458+
Some action)
454459

455460
let t = { Code_action.kind = RefactorInline; run = code_action }

ocaml-lsp-server/src/code_actions/action_mark_remove_unused.ml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ let rec mark_value_unused_edit name contexts =
7171

7272
let code_action_mark_value_unused doc (diagnostic : Diagnostic.t) =
7373
let open Option.O in
74-
Document.with_pipeline_exn doc (fun pipeline ->
74+
Document.Merlin.with_pipeline_exn (Document.merlin_exn doc) (fun pipeline ->
7575
let var_name = slice doc diagnostic.range in
7676
let pos = diagnostic.range.start in
7777
let+ text_edit =
@@ -125,10 +125,9 @@ let code_action_remove_range doc (diagnostic : Diagnostic.t) range =
125125

126126
(* Create a code action that removes the value mentioned in [diagnostic]. *)
127127
let code_action_remove_value doc pos (diagnostic : Diagnostic.t) =
128-
Document.with_pipeline_exn doc (fun pipeline ->
128+
Document.Merlin.with_pipeline_exn (Document.merlin_exn doc) (fun pipeline ->
129129
let var_name = slice doc diagnostic.range in
130-
enclosing_pos pipeline pos
131-
|> List.map ~f:(fun (_, x) -> x)
130+
enclosing_pos pipeline pos |> List.map ~f:snd
132131
|> enclosing_value_binding_range var_name
133132
|> Option.map ~f:(fun range ->
134133
code_action_remove_range doc diagnostic range))

ocaml-lsp-server/src/code_actions/action_refactor_open.ml

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,33 @@ open Fiber.O
33

44
let code_action (mode : [ `Qualify | `Unqualify ]) (action_kind : string) doc
55
(params : CodeActionParams.t) =
6-
let+ res =
7-
let command =
8-
let pos_start = Position.logical params.range.start in
9-
Query_protocol.Refactor_open (mode, pos_start)
6+
match Document.kind doc with
7+
| `Other -> Fiber.return None
8+
| `Merlin doc -> (
9+
let+ res =
10+
let command =
11+
let pos_start = Position.logical params.range.start in
12+
Query_protocol.Refactor_open (mode, pos_start)
13+
in
14+
Document.Merlin.dispatch_exn doc command
1015
in
11-
Document.dispatch_exn doc command
12-
in
13-
match res with
14-
| [] -> None
15-
| changes ->
16-
let code_action =
17-
let edit : WorkspaceEdit.t =
18-
let edits =
19-
List.map changes ~f:(fun (newText, loc) ->
20-
{ TextEdit.newText; range = Range.of_loc loc })
16+
match res with
17+
| [] -> None
18+
| changes ->
19+
let code_action =
20+
let edit : WorkspaceEdit.t =
21+
let edits =
22+
List.map changes ~f:(fun (newText, loc) ->
23+
{ TextEdit.newText; range = Range.of_loc loc })
24+
in
25+
let uri = params.textDocument.uri in
26+
WorkspaceEdit.create ~changes:[ (uri, edits) ] ()
2127
in
22-
let uri = params.textDocument.uri in
23-
WorkspaceEdit.create ~changes:[ (uri, edits) ] ()
28+
let kind = CodeActionKind.Other action_kind in
29+
let title = String.capitalize_ascii action_kind in
30+
CodeAction.create ~title ~kind ~edit ~isPreferred:false ()
2431
in
25-
let kind = CodeActionKind.Other action_kind in
26-
let title = String.capitalize_ascii action_kind in
27-
CodeAction.create ~title ~kind ~edit ~isPreferred:false ()
28-
in
29-
Some code_action
32+
Some code_action)
3033

3134
let unqualify =
3235
let action_kind = "remove module name from identifiers" in

ocaml-lsp-server/src/code_actions/action_type_annotate.ml

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,25 +44,31 @@ let code_action_of_type_enclosing uri doc (loc, typ) =
4444
()
4545

4646
let code_action doc (params : CodeActionParams.t) =
47-
let pos_start = Position.logical params.range.start in
48-
let+ res =
49-
Document.with_pipeline_exn doc (fun pipeline ->
50-
let context = check_typeable_context pipeline pos_start in
51-
match context with
52-
| `Invalid -> None
53-
| `Valid ->
54-
let command = Query_protocol.Type_enclosing (None, pos_start, None) in
55-
let config = Mpipeline.final_config pipeline in
56-
let config =
57-
{ config with query = { config.query with verbosity = 0 } }
58-
in
59-
let pipeline = Mpipeline.make config (Document.source doc) in
60-
Some (Query_commands.dispatch pipeline command))
61-
in
62-
match res with
63-
| None | Some [] | Some ((_, `Index _, _) :: _) -> None
64-
| Some ((location, `String value, _) :: _) ->
65-
code_action_of_type_enclosing params.textDocument.uri doc (location, value)
47+
match Document.kind doc with
48+
| `Other -> Fiber.return None
49+
| `Merlin merlin -> (
50+
let pos_start = Position.logical params.range.start in
51+
let+ res =
52+
Document.Merlin.with_pipeline_exn merlin (fun pipeline ->
53+
let context = check_typeable_context pipeline pos_start in
54+
match context with
55+
| `Invalid -> None
56+
| `Valid ->
57+
let command =
58+
Query_protocol.Type_enclosing (None, pos_start, None)
59+
in
60+
let config = Mpipeline.final_config pipeline in
61+
let config =
62+
{ config with query = { config.query with verbosity = 0 } }
63+
in
64+
let pipeline = Mpipeline.make config (Document.source doc) in
65+
Some (Query_commands.dispatch pipeline command))
66+
in
67+
match res with
68+
| None | Some [] | Some ((_, `Index _, _) :: _) -> None
69+
| Some ((location, `String value, _) :: _) ->
70+
code_action_of_type_enclosing params.textDocument.uri doc (location, value)
71+
)
6672

6773
let t =
6874
{ Code_action.kind = CodeActionKind.Other action_kind; run = code_action }

0 commit comments

Comments
 (0)