diff --git a/src/compiler/compilationCache.ml b/src/compiler/compilationCache.ml index 610c970b62f..8e823dd7ed2 100644 --- a/src/compiler/compilationCache.ml +++ b/src/compiler/compilationCache.ml @@ -36,6 +36,7 @@ class context_cache (index : int) (sign : Digest.t) = object(self) val modules : (path,module_def) Hashtbl.t = Hashtbl.create 0 val binary_cache : (path,HxbData.module_cache) Hashtbl.t = Hashtbl.create 0 val tmp_binary_cache : (path,HxbData.module_cache) Hashtbl.t = Hashtbl.create 0 + val tmp_parse_cache : (Path.UniqueKey.t,(string list * type_decl list) Parser.parse_result) Hashtbl.t = Hashtbl.create 0 val get_hxb_module_mutex = Mutex.create () val removed_files = Hashtbl.create 0 val mutable json = JNull @@ -110,7 +111,14 @@ class context_cache (index : int) (sign : Digest.t) = object(self) Hashtbl.replace modules path m method clear_temp_cache = - Hashtbl.clear tmp_binary_cache + Hashtbl.clear tmp_binary_cache; + Hashtbl.clear tmp_parse_cache + + method find_tmp_parse key = + Hashtbl.find tmp_parse_cache key + + method cache_tmp_parse key r = + Hashtbl.replace tmp_parse_cache key r method clear_cache = Hashtbl.clear modules; diff --git a/src/compiler/server/serverCache.ml b/src/compiler/server/serverCache.ml index 116ea9a55b4..d97c9b78a77 100644 --- a/src/compiler/server/serverCache.ml +++ b/src/compiler/server/serverCache.ml @@ -14,9 +14,19 @@ let parse_file sctx com (rfile : ClassPaths.resolved_file) p = let ffile = Path.get_full_path rfile.file and fkey = com.part_scope.file_keys#get file in let is_display_file = DisplayPosition.display_position#is_in_file (com.part_scope.file_keys#get ffile) in + let has_request_contents = + com.file_contents <> [] && (try List.assoc fkey com.file_contents <> None with Not_found -> false) + in match is_display_file, sctx.ServerCompilationContext.current_stdin with | true, Some stdin when (com.file_contents <> [] || Common.defined com Define.DisplayStdin) -> TypeloadParse.parse_file_from_string com file p stdin + | _ when has_request_contents -> + (try + cc#find_tmp_parse fkey + with Not_found -> + let r = TypeloadParse.parse_file com rfile p in + cc#cache_tmp_parse fkey r; + r) | _ -> let ftime = file_time ffile in let data = Std.finally (Timer.start_timer com.timer_ctx ["server";"parser cache"]) (fun () -> @@ -213,6 +223,15 @@ let check_module sctx com m_path m_extra p = end end in + let check_request_contents () = + let file = Path.UniqueKey.lazy_path m_extra.m_file in + let fkey = Path.UniqueKey.lazy_key m_extra.m_file in + let has_contents = (try List.assoc fkey com.file_contents <> None with Not_found -> false) in + if has_contents && content_changed m_path file then begin + ServerMessage.not_cached com "" m_path; + raise (Dirty (FileChanged file)) + end + in let find_module_extra sign mpath = (com.cs#get_context sign)#find_module_extra mpath in @@ -234,6 +253,7 @@ let check_module sctx com m_path m_extra p = try check_module_path(); if not (has_policy NoFileSystemCheck) || Path.file_extension (Path.UniqueKey.lazy_path m_extra.m_file) <> "hx" then check_file(); + if com.file_contents <> [] then check_request_contents(); if (get_typing_mode com m_extra) = FullTyping then check_dependencies(); None with diff --git a/src/context/display/displayJson.ml b/src/context/display/displayJson.ml index 1e1971eb117..8e4c4f05dbd 100644 --- a/src/context/display/displayJson.ml +++ b/src/context/display/displayJson.ml @@ -88,6 +88,10 @@ class display_handler (jsonrpc : jsonrpc_handler) com (cs : CompilationCache.t) pmax = pos; }; + let contents = match contents with + | Some s when (try Std.input_file ~bin:true file = s with _ -> false) -> None + | c -> c + in com.file_contents <- [file_unique, contents]; end else begin let file_contents = jsonrpc#get_opt_param (fun () -> @@ -103,6 +107,10 @@ class display_handler (jsonrpc : jsonrpc_handler) com (cs : CompilationCache.t) let s = jsonrpc#get_string_field "fileContents" "contents" fl in Some s ) None in + let contents = match contents with + | Some s when (try Std.input_file ~bin:true file = s with _ -> false) -> None + | c -> c + in (file_unique, contents) | _ -> invalid_arg "fileContents" ) file_contents in diff --git a/src/context/display/displayTexpr.ml b/src/context/display/displayTexpr.ml index dc022a193b0..00cc14dd80b 100644 --- a/src/context/display/displayTexpr.ml +++ b/src/context/display/displayTexpr.ml @@ -166,8 +166,13 @@ let check_display_file ctx cs = | Some cc -> begin try let p = DisplayPosition.display_position#get in - let cfile = cc#find_file (ctx.com.part_scope.file_keys#get p.pfile) in + let fkey = ctx.com.part_scope.file_keys#get p.pfile in + let cfile = cc#find_file fkey in let path = (cfile.c_package,get_module_name_of_cfile p.pfile cfile) in + if (try List.assoc fkey ctx.com.file_contents <> None with Not_found -> false) then begin + let _,_,_,decls,_ = TypeloadParse.parse_module' ctx.com path null_pos in + if decls <> cfile.c_decls then raise Not_found + end; TypeloadParse.PdiHandler.handle_pdi ctx.com cfile.c_pdi; (* We have to go through type_module_hook because one of the module's dependencies could be invalid (issue #8991). *) diff --git a/tests/server/src/cases/UnsavedContentsStale.hx b/tests/server/src/cases/UnsavedContentsStale.hx new file mode 100644 index 00000000000..2ab6fd61c4c --- /dev/null +++ b/tests/server/src/cases/UnsavedContentsStale.hx @@ -0,0 +1,28 @@ +package cases; + +import haxe.display.FsPath; +import haxe.display.Server; +import TestCase; +import utest.Assert; + +// A hover carrying unsaved buffer contents must be answered against THOSE contents even when the +// server has a cached parse of the file whose disk mtime still matches (file not saved). Models +// vshaxe: edit -> (invalidate deduplicated away / compile re-cached the disk parse) -> hover. +class UnsavedContentsStale extends TestCase { + static inline var V1 = "class A {\n\tstatic function main() {\n\t\tvar aaa = 42;\n\t\ttrace(aaa);\n\t}\n}"; + static inline var V2 = "class A {\n\tstatic function main() {\n\t\tvar bbb = \"s\";\n\t\tvar aaa = 42;\n\t\ttrace(aaa);\n\t}\n}"; + + function test(_) { + vfs.putContent("A.hx", V1); + var args = ["-main", "A", "-js", "no.js", "--no-output"]; + runHaxe(args); + assertSuccess(); + + var offset = V1.indexOf("aaa") + 1; // == V2.indexOf("bbb") + 1 + + // NO invalidate: the compile above cached A's disk parse; hover sends newer unsaved contents. + var res = runHaxeJson(args, DisplayMethods.Hover, {file: new FsPath("A.hx"), offset: offset, contents: V2}); + var t = if (res == null || res.item == null) "" else try res.item.type.args.path.typeName catch (_) ""; + Assert.equals("String", t, "got " + t); + } +}