Skip to content

Commit

Permalink
[flow][autocomplete] cache artifacts in autocomplete session
Browse files Browse the repository at this point in the history
Summary:
This diff applies caching of type artifacts for LSP requests in an autocomplete session. For example in the session below we expect to see the following hits/misses:
```
foo.|        // cache miss
foo.b|       // cache hit
foo.ba|      // cache hit
foo.bar|     // cache hit
```
This is done similar to how other LSP commands do it, but with a few key differences:
* The cached artifacts are adapted to the needs of autocomplete. We store a different map in the persistent client, with the following contents:
  - We use `Typed_ast_utils.available_ast` (which is an option between typed AST and type env), instead of only typed AST, since autocomplete does not need to compute the entire typed AST (under on-demand mode).
  - We cache the canonical contents for the autocomplete session, so that we can compare them with the next AC request we get.
* Eviction differs in that we don't remove artifacts on `didChange`. It is expected that contents will change during an AC session, but canonical contents will not.
* A cache hit is when canonical contents of cached version match current contents. (Adjusted `with_cache_sync` slightly to support this.)
* On a cache miss we still do autocomplete's special type inference (D55080918)

Changelog: [internal]

Reviewed By: SamChou19815

Differential Revision: D55456832

fbshipit-source-id: 3deb952807f6c1d463aef81883d9e4346864894f
  • Loading branch information
panagosg7 authored and facebook-github-bot committed Apr 11, 2024
1 parent 581f9cf commit f548c45
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 18 deletions.
6 changes: 3 additions & 3 deletions src/common/utils/cache.ml
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ module Make (Map : WrappedMap.S) = struct
Lwt.return (value, false)
| Some result -> Lwt.return (result, true)

let with_cache_sync key value cache =
let with_cache_sync ~cond key value cache =
let cached_result = get_from_cache key cache in
match cached_result with
| None ->
| Some result when cond result -> (result, true)
| _ ->
let value = Lazy.force value in
add_after_miss key value cache;
(value, false)
| Some result -> (result, true)
end
2 changes: 1 addition & 1 deletion src/common/utils/cache_sig.ml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ module type S = sig
* missed. *)
val with_cache : key -> 'a Lwt.t Lazy.t -> 'a t -> ('a * bool) Lwt.t

val with_cache_sync : key -> 'a Lazy.t -> 'a t -> 'a * bool
val with_cache_sync : cond:('a -> bool) -> key -> 'a Lazy.t -> 'a t -> 'a * bool
end
59 changes: 51 additions & 8 deletions src/server/command_handler/commandHandler.ml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ let type_parse_artifacts_with_cache
(Lazy.force artifacts)
)
in
let (result, did_hit) = FilenameCache.with_cache_sync file lazy_result cache in
let (result, did_hit) =
FilenameCache.with_cache_sync ~cond:(fun _ -> true) file lazy_result cache
in
(result, Some did_hit)

let add_cache_hit_data_to_json json_props did_hit =
Expand Down Expand Up @@ -407,13 +409,36 @@ let type_check_for_autocomplete ~options ~profiling master_cx filename parse_art
in
(cx, Typed_ast_utils.ALoc_ast aloc_ast)

let type_parse_artifacts_for_ac_with_cache
~options ~profiling ~type_parse_artifacts_cache ~cached master_cx file contents artifacts =
let type_parse_artifacts =
lazy
(match Lazy.force artifacts with
| (None, errs) -> Error errs
| (Some parse_artifacts, _errs) ->
let (cx, available_ast) =
type_check_for_autocomplete ~options ~profiling master_cx file parse_artifacts
in
Ok (contents, parse_artifacts, cx, available_ast))
in
let cond = function
| Error _ -> false (* we can't tell there's a match *)
| Ok (contents', _, _, _) -> contents = contents'
in
match type_parse_artifacts_cache with
| Some cache when cached ->
let (result, did_hit) = FilenameCache.with_cache_sync ~cond file type_parse_artifacts cache in
(result, Some did_hit)
| _ -> (Lazy.force type_parse_artifacts, None)

let autocomplete_on_parsed
~filename
~contents
~trigger_character
~reader
~options
~env
~client
~profiling
~cursor
~imports
Expand All @@ -440,22 +465,36 @@ let autocomplete_on_parsed
Base.Option.value_map ~default:cursor_loc ~f:Autocomplete_sigil.Canonical.cursor canon_token
in
Autocomplete_js.autocomplete_set_hooks ~cursor:canon_cursor;
let parse_result = Type_contents.parse_contents ~options ~profiling contents filename in
let initial_json_props =
let open Hh_json in
[
("ac_trigger", JSON_String (Base.Option.value trigger_character ~default:"None"));
("broader_context", JSON_String broader_context);
]
in
let type_parse_artifacts_cache =
Base.Option.map client ~f:Persistent_connection.autocomplete_artifacts_cache
in
(* Parse the canonical contents into a canonical AST. *)
let parse_result = lazy (Type_contents.parse_contents ~options ~profiling contents filename) in
(* Perform type inference over the canonical AST. *)
let (file_artifacts_result, did_hit) =
type_parse_artifacts_for_ac_with_cache
~options
~profiling
~type_parse_artifacts_cache
~cached:canonical
env.master_cx
filename
contents
parse_result
in
let initial_json_props = add_cache_hit_data_to_json initial_json_props did_hit in
let ac_typing_artifacts =
match parse_result with
| (None, _parse_errors) -> None
| (Some parse_artifacts, _errs) ->
match file_artifacts_result with
| Error _ -> None
| Ok (_contents, parse_artifacts, cx, available_ast) ->
let (Parse_artifacts { docblock = info; file_sig; ast; parse_errors; _ }) = parse_artifacts in
let (cx, available_ast) =
type_check_for_autocomplete ~options ~profiling env.master_cx filename parse_artifacts
in
Some (info, file_sig, ast, parse_errors, cx, available_ast)
in
let ac_result =
Expand Down Expand Up @@ -502,6 +541,7 @@ let autocomplete
~reader
~options
~env
~client
~profiling
~input
~cursor
Expand All @@ -527,6 +567,7 @@ let autocomplete
~reader
~options
~env
~client
~profiling
~cursor
~imports
Expand Down Expand Up @@ -1213,6 +1254,7 @@ let handle_autocomplete
~reader
~options
~env
~client:None
~profiling
~input
~cursor
Expand Down Expand Up @@ -2334,6 +2376,7 @@ let handle_persistent_autocomplete_lsp
~reader
~options
~env
~client:(Some client)
~profiling
~input:file_input
~cursor:(line, char)
Expand Down
20 changes: 14 additions & 6 deletions src/server/persistent_connection/persistent_connection.ml
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,20 @@ type single_client = {
mutable outstanding_handlers: unit Lsp.lsp_handler Lsp.IdMap.t;
mutable autocomplete_token: autocomplete_token option;
mutable autocomplete_session_length: int;
autocomplete_artifacts_cache:
(Types_js_types.autocomplete_artifacts, Flow_error.ErrorSet.t) result FilenameCache.t;
}

type t = Prot.client_id list

let cache_max_size = 10

let remove_cache_entry client filename =
let remove_cache_entry ~autocomplete client filename =
(* get_def, coverage, etc. all construct a File_key.SourceFile, which is then used as a key
* here. *)
let file_key = File_key.SourceFile filename in
FilenameCache.remove_entry file_key client.type_parse_artifacts_cache
FilenameCache.remove_entry file_key client.type_parse_artifacts_cache;
if autocomplete then FilenameCache.remove_entry file_key client.autocomplete_artifacts_cache

let active_clients : single_client IMap.t ref = ref IMap.empty

Expand Down Expand Up @@ -132,6 +135,7 @@ let add_client client_id lsp_initialize_params =
outstanding_handlers = Lsp.IdMap.empty;
autocomplete_token = None;
autocomplete_session_length = 0;
autocomplete_artifacts_cache = FilenameCache.make ~max_size:cache_max_size;
}
in
active_clients := IMap.add client_id new_client !active_clients;
Expand Down Expand Up @@ -192,7 +196,7 @@ let client_did_open (client : single_client) ~(files : (string * string) Nel.t)
(match Nel.length files with
| 1 -> Hh_logger.info "Client #%d opened %s" client.client_id (files |> Nel.hd |> fst)
| len -> Hh_logger.info "Client #%d opened %d files" client.client_id len);
Nel.iter (fun (filename, _content) -> remove_cache_entry client filename) files;
Nel.iter (fun (filename, _content) -> remove_cache_entry ~autocomplete:true client filename) files;
let add_file acc (filename, content) = SMap.add filename content acc in
let new_opened_files = Nel.fold_left add_file client.opened_files files in
(* SMap.add ensures physical equality if the map is unchanged, since 4.0.3,
Expand All @@ -210,7 +214,7 @@ let client_did_change
(fn : string)
(changes : Lsp.DidChange.textDocumentContentChangeEvent list) :
(unit, string * Utils.callstack) result =
remove_cache_entry client fn;
remove_cache_entry ~autocomplete:false client fn;
try
let content = SMap.find fn client.opened_files in
match Lsp_helpers.apply_changes content changes with
Expand All @@ -229,7 +233,7 @@ let client_did_close (client : single_client) ~(filenames : string Nel.t) : bool
(match Nel.length filenames with
| 1 -> Hh_logger.info "Client #%d closed %s" client.client_id (filenames |> Nel.hd)
| len -> Hh_logger.info "Client #%d closed %d files" client.client_id len);
Nel.iter (remove_cache_entry client) filenames;
Nel.iter (remove_cache_entry ~autocomplete:true client) filenames;
let remove_file acc filename = SMap.remove filename acc in
let new_opened_files = Nel.fold_left remove_file client.opened_files filenames in
(* SMap.remove ensures physical equality if the set is unchanged,
Expand Down Expand Up @@ -289,9 +293,13 @@ let client_config client = client.client_config

let type_parse_artifacts_cache client = client.type_parse_artifacts_cache

let autocomplete_artifacts_cache client = client.autocomplete_artifacts_cache

let clear_type_parse_artifacts_caches () =
IMap.iter
(fun _key client -> FilenameCache.clear client.type_parse_artifacts_cache)
(fun _key client ->
FilenameCache.clear client.type_parse_artifacts_cache;
FilenameCache.clear client.autocomplete_artifacts_cache)
!active_clients

let push_outstanding_handler client id handler =
Expand Down
4 changes: 4 additions & 0 deletions src/server/persistent_connection/persistent_connection.mli
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ val client_config : single_client -> Client_config.t
val type_parse_artifacts_cache :
single_client -> (Types_js_types.file_artifacts, Flow_error.ErrorSet.t) result FilenameCache.t

val autocomplete_artifacts_cache :
single_client ->
(Types_js_types.autocomplete_artifacts, Flow_error.ErrorSet.t) result FilenameCache.t

val clear_type_parse_artifacts_caches : unit -> unit

val push_outstanding_handler : single_client -> Lsp.lsp_id -> unit Lsp.lsp_handler -> unit
Expand Down
3 changes: 3 additions & 0 deletions src/services/inference/types/types_js_types.ml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ type typecheck_artifacts =

type file_artifacts = parse_artifacts * typecheck_artifacts

type autocomplete_artifacts =
string (* contents *) * parse_artifacts * Context.t * Typed_ast_utils.available_ast

type duration = float

type check_type_result =
Expand Down

0 comments on commit f548c45

Please sign in to comment.