From 036d8bba7abbbae20a7956825c1a12e7f870977c Mon Sep 17 00:00:00 2001 From: Yuxiao Mao Date: Mon, 20 Jul 2026 08:58:21 +0200 Subject: [PATCH 1/2] [display] Fix missing field mismatch error after display requests --- src/compiler/displayProcessing.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/displayProcessing.ml b/src/compiler/displayProcessing.ml index 14ac98308e1..ed2181c0f06 100644 --- a/src/compiler/displayProcessing.ml +++ b/src/compiler/displayProcessing.ml @@ -190,7 +190,7 @@ let maybe_load_display_file_before_typing tctx display_file_dot_path = match dis (* 5. Display processing after typing *) let handle_display_after_typing com tctx display_file_dot_path = - if com.display.dms_kind = DMNone && com.part_scope.has_error && Common.is_compilation com then + if com.display.dms_kind = DMNone && com.part_scope.has_error then true else begin begin match com.display.dms_kind,Atomic.get com.part_scope.parser_state.delayed_syntax_completion with From c6795030b0950dca2e182642b629e66e7508c0cb Mon Sep 17 00:00:00 2001 From: Rudy Ges Date: Mon, 20 Jul 2026 09:26:41 +0200 Subject: [PATCH 2/2] [server] do not reuse modules whose errors were downgraded to diagnostics Typing in diagnostics mode turns errors like missing fields into diagnostics-only messages, which are filtered out when replayed outside of diagnostics mode. Reusing such a module in a compilation therefore silently dropped the error. Mark those modules dirty for non-diagnostics requests so they get typed again. Closes #12995 --- src/compiler/displayProcessing.ml | 2 +- src/compiler/io/compilerMessage.ml | 6 +----- src/compiler/server/serverCache.ml | 13 +++++++++++++ src/context/common.ml | 4 +--- src/core/message.ml | 6 ++++++ src/core/tPrinting.ml | 1 + src/core/tType.ml | 1 + tests/server/src/cases/issues/Issue12995.hx | 17 +++++++++++++++++ .../test/templates/issues/Issue12995/Main.hx | 7 +++++++ 9 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 tests/server/src/cases/issues/Issue12995.hx create mode 100644 tests/server/test/templates/issues/Issue12995/Main.hx diff --git a/src/compiler/displayProcessing.ml b/src/compiler/displayProcessing.ml index ed2181c0f06..14ac98308e1 100644 --- a/src/compiler/displayProcessing.ml +++ b/src/compiler/displayProcessing.ml @@ -190,7 +190,7 @@ let maybe_load_display_file_before_typing tctx display_file_dot_path = match dis (* 5. Display processing after typing *) let handle_display_after_typing com tctx display_file_dot_path = - if com.display.dms_kind = DMNone && com.part_scope.has_error then + if com.display.dms_kind = DMNone && com.part_scope.has_error && Common.is_compilation com then true else begin begin match com.display.dms_kind,Atomic.get com.part_scope.parser_state.delayed_syntax_completion with diff --git a/src/compiler/io/compilerMessage.ml b/src/compiler/io/compilerMessage.ml index b5f13e8529f..259c7b1decd 100644 --- a/src/compiler/io/compilerMessage.ml +++ b/src/compiler/io/compilerMessage.ml @@ -85,11 +85,7 @@ let replay_message com cm = | MKWarning(w, options) -> com.warning ~depth:cm.cm_depth w options cm.cm_message cm.cm_pos | _ -> - let is_diagnostics_only = match cm.cm_diagnostics_kind with - | MessageKind.DKMissingFields | MessageKind.DKUnresolvedIdentifier -> true - | _ -> false - in - if not is_diagnostics_only || is_diagnostics com then + if not (Message.cm_is_diagnostics_only cm) || is_diagnostics com then com.part_scope.messages <- cm :: com.part_scope.messages (* Default handlers *) diff --git a/src/compiler/server/serverCache.ml b/src/compiler/server/serverCache.ml index 116ea9a55b4..4d63075f283 100644 --- a/src/compiler/server/serverCache.ml +++ b/src/compiler/server/serverCache.ml @@ -230,8 +230,21 @@ let check_module sctx com m_path m_extra p = | Some reason -> raise (Dirty (DependencyDirty(mpath,reason))) ) m_extra.m_deps in + (* Errors like missing fields are downgraded to diagnostics-only messages when typing in + diagnostics mode. Such a module cannot be reused by an actual compilation, which has to + type it again in order to produce the real error (#12995). *) + let check_diagnostics_only_errors () = + if not (is_diagnostics com) && DynArray.fold_left (fun acc cbo -> acc || match cbo with + | Message cm -> + Message.cm_is_diagnostics_only cm + | _ -> + false + ) false m_extra.m_cache_bound_objects then + raise (Dirty (Tainted DiagnosticsOnlyErrors)) + in let check () = try + check_diagnostics_only_errors(); check_module_path(); if not (has_policy NoFileSystemCheck) || Path.file_extension (Path.UniqueKey.lazy_path m_extra.m_file) <> "hx" then check_file(); if (get_typing_mode com m_extra) = FullTyping then check_dependencies(); diff --git a/src/context/common.ml b/src/context/common.ml index c34adbfc85e..05acc818566 100644 --- a/src/context/common.ml +++ b/src/context/common.ml @@ -831,9 +831,7 @@ let is_compilation com = com.display.dms_kind = DMNone && not (is_diagnostics co not prevent context caching. *) let has_error_to_report com = let has_reportable_message = List.exists (fun cm -> - match cm.cm_diagnostics_kind with - | MessageKind.DKMissingFields | MessageKind.DKUnresolvedIdentifier -> false - | _ -> true + not (Message.cm_is_diagnostics_only cm) ) com.part_scope.messages in com.part_scope.has_error && (is_compilation com || has_reportable_message) diff --git a/src/core/message.ml b/src/core/message.ml index 381bab21d83..9815da46eec 100644 --- a/src/core/message.ml +++ b/src/core/message.ml @@ -67,6 +67,12 @@ type t = { let cm_severity cm = message_kind_severity cm.cm_message_kind +(** Messages which only exist in diagnostics mode, where the corresponding error is downgraded to + diagnostics data instead of being reported. *) +let cm_is_diagnostics_only cm = match cm.cm_diagnostics_kind with + | MessageKind.DKMissingFields | MessageKind.DKUnresolvedIdentifier -> true + | _ -> false + let cm_code cm = match cm.cm_message_kind with | MKWarning(w,_) -> let wobj = WarningList.warning_obj w in diff --git a/src/core/tPrinting.ml b/src/core/tPrinting.ml index b91205d1d8f..2ac242e2f8e 100644 --- a/src/core/tPrinting.ml +++ b/src/core/tPrinting.ml @@ -651,6 +651,7 @@ module Printer = struct | ServerInvalidate -> "server/invalidate" | ServerInvalidateFiles -> "server_invalidate_files" | ServerInvalidateModule -> "server_invalidate_module" + | DiagnosticsOnlyErrors -> "diagnostics_only_errors" let s_module_skip_reason reason = let rec loop stack = function diff --git a/src/core/tType.ml b/src/core/tType.ml index e7efeec516e..e52db71e6dd 100644 --- a/src/core/tType.ml +++ b/src/core/tType.ml @@ -38,6 +38,7 @@ type module_tainting_reason = | ServerInvalidate | ServerInvalidateFiles | ServerInvalidateModule + | DiagnosticsOnlyErrors type module_skip_reason = | DependencyDirty of path * module_skip_reason diff --git a/tests/server/src/cases/issues/Issue12995.hx b/tests/server/src/cases/issues/Issue12995.hx new file mode 100644 index 00000000000..8591cd97296 --- /dev/null +++ b/tests/server/src/cases/issues/Issue12995.hx @@ -0,0 +1,17 @@ +package cases.issues; + +class Issue12995 extends TestCase { + function test(_) { + vfs.putContent("Main.hx", getTemplate("issues/Issue12995/Main.hx")); + var args = ["-main", "Main"]; + + runHaxe(args); + assertErrorMessage("Class has no field bar"); + + runHaxeJson(args, DisplayMethods.Diagnostics, {file: new FsPath("Main.hx")}); + + // The diagnostics request must not cache the erroring module (#12995) + runHaxe(args); + assertErrorMessage("Class has no field bar"); + } +} diff --git a/tests/server/test/templates/issues/Issue12995/Main.hx b/tests/server/test/templates/issues/Issue12995/Main.hx new file mode 100644 index 00000000000..0dda6517bb7 --- /dev/null +++ b/tests/server/test/templates/issues/Issue12995/Main.hx @@ -0,0 +1,7 @@ +class Foo {} + +class Main { + static function main() { + Foo.bar(); + } +}