From bef511da6b05c4096b28116bebdecdbea4b6392d Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Fri, 27 Feb 2026 12:11:10 +0100 Subject: [PATCH] Reanalyze server: invalidate cache on config change Re-read rescript.json before each server request and recreate the reactive pipeline from scratch when the reanalyze config changes (suppress, unsuppress, analysis mode, transitive). Closes rescript-lang/rescript-vscode#1180 Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.md | 2 + analysis/reanalyze/src/ReanalyzeServer.ml | 97 +++++++++++-------- analysis/reanalyze/src/RunConfig.ml | 29 ++++++ .../deadcode/test-reactive-server.sh | 77 ++++++++++++++- 4 files changed, 165 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88c60f35bd..aa18d8a5ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ #### :bug: Bug fix +- Reanalyze server: invalidate cache and recompute results when config changes in `rescript.json`. https://github.com/rescript-lang/rescript/pull/8262 + #### :memo: Documentation #### :nail_care: Polish diff --git a/analysis/reanalyze/src/ReanalyzeServer.ml b/analysis/reanalyze/src/ReanalyzeServer.ml index 4db8480603..09ceb3f5ec 100644 --- a/analysis/reanalyze/src/ReanalyzeServer.ml +++ b/analysis/reanalyze/src/ReanalyzeServer.ml @@ -96,6 +96,14 @@ module Server = struct let s = Gc.quick_stat () in mb_of_words s.live_words + type reactive_pipeline = { + dce_config: DceConfig.t; + reactive_collection: ReactiveAnalysis.t; + reactive_merge: ReactiveMerge.t; + reactive_liveness: ReactiveLiveness.t; + reactive_solver: ReactiveSolver.t; + } + type server_state = { parse_argv: string array -> string option; run_analysis: @@ -111,12 +119,9 @@ module Server = struct unit; config: server_config; cmtRoot: string option; - dce_config: DceConfig.t; - reactive_collection: ReactiveAnalysis.t; - reactive_merge: ReactiveMerge.t; - reactive_liveness: ReactiveLiveness.t; - reactive_solver: ReactiveSolver.t; + mutable pipeline: reactive_pipeline; stats: server_stats; + mutable config_snapshot: RunConfig.snapshot; } type request_info = { @@ -261,6 +266,32 @@ Examples: unlink_if_exists stderr_path) run + let create_reactive_pipeline () : reactive_pipeline = + let dce_config = DceConfig.current () in + let reactive_collection = ReactiveAnalysis.create ~config:dce_config in + let file_data_collection = + ReactiveAnalysis.to_file_data_collection reactive_collection + in + let reactive_merge = ReactiveMerge.create file_data_collection in + let reactive_liveness = ReactiveLiveness.create ~merged:reactive_merge in + let value_refs_from = + if dce_config.DceConfig.run.transitive then None + else Some reactive_merge.ReactiveMerge.value_refs_from + in + let reactive_solver = + ReactiveSolver.create ~decls:reactive_merge.ReactiveMerge.decls + ~live:reactive_liveness.ReactiveLiveness.live + ~annotations:reactive_merge.ReactiveMerge.annotations ~value_refs_from + ~config:dce_config + in + { + dce_config; + reactive_collection; + reactive_merge; + reactive_liveness; + reactive_solver; + } + let init_state ~(parse_argv : string array -> string option) ~(run_analysis : dce_config:DceConfig.t -> @@ -291,39 +322,16 @@ Examples: server with editor-like args only." !Cli.churn else - let dce_config = DceConfig.current () in - let reactive_collection = - ReactiveAnalysis.create ~config:dce_config - in - let file_data_collection = - ReactiveAnalysis.to_file_data_collection reactive_collection - in - let reactive_merge = ReactiveMerge.create file_data_collection in - let reactive_liveness = - ReactiveLiveness.create ~merged:reactive_merge - in - let value_refs_from = - if dce_config.DceConfig.run.transitive then None - else Some reactive_merge.ReactiveMerge.value_refs_from - in - let reactive_solver = - ReactiveSolver.create ~decls:reactive_merge.ReactiveMerge.decls - ~live:reactive_liveness.ReactiveLiveness.live - ~annotations:reactive_merge.ReactiveMerge.annotations - ~value_refs_from ~config:dce_config - in + let pipeline = create_reactive_pipeline () in Ok { parse_argv; run_analysis; config; cmtRoot; - dce_config; - reactive_collection; - reactive_merge; - reactive_liveness; - reactive_solver; + pipeline; stats = {request_count = 0}; + config_snapshot = RunConfig.snapshot (); }) let run_one_request (state : server_state) (_req : request) : @@ -347,6 +355,17 @@ Examples: (* Always run from the server's project root; client cwd is not stable in VS Code. *) state.config.cwd (fun () -> capture_stdout_stderr (fun () -> + (* Re-read config from rescript.json to detect changes. + If changed, recreate the entire reactive pipeline from scratch. *) + RunConfig.reset (); + Paths.Config.processConfig (); + let new_snapshot = RunConfig.snapshot () in + if + not + (RunConfig.equal_snapshot state.config_snapshot new_snapshot) + then ( + state.pipeline <- create_reactive_pipeline (); + state.config_snapshot <- new_snapshot); Log_.Color.setup (); Timing.enabled := !Cli.timing; Reactive.set_debug !Cli.timing; @@ -357,18 +376,18 @@ Examples: (* Match direct CLI output (a leading newline before the JSON array). *) Printf.printf "\n"; EmitJson.start (); - state.run_analysis ~dce_config:state.dce_config - ~cmtRoot:state.cmtRoot - ~reactive_collection:(Some state.reactive_collection) - ~reactive_merge:(Some state.reactive_merge) - ~reactive_liveness:(Some state.reactive_liveness) - ~reactive_solver:(Some state.reactive_solver) ~skip_file:None + let p = state.pipeline in + state.run_analysis ~dce_config:p.dce_config ~cmtRoot:state.cmtRoot + ~reactive_collection:(Some p.reactive_collection) + ~reactive_merge:(Some p.reactive_merge) + ~reactive_liveness:(Some p.reactive_liveness) + ~reactive_solver:(Some p.reactive_solver) ~skip_file:None ~file_stats (); issue_count := Log_.Stats.get_issue_count (); - let d, l = ReactiveSolver.stats ~t:state.reactive_solver in + let d, l = ReactiveSolver.stats ~t:p.reactive_solver in dead_count := d; live_count := l; - Log_.Stats.report ~config:state.dce_config; + Log_.Stats.report ~config:p.dce_config; Log_.Stats.clear (); EmitJson.finish ()) |> response_of_result) diff --git a/analysis/reanalyze/src/RunConfig.ml b/analysis/reanalyze/src/RunConfig.ml index 3c33f79909..b6b1d28008 100644 --- a/analysis/reanalyze/src/RunConfig.ml +++ b/analysis/reanalyze/src/RunConfig.ml @@ -21,6 +21,14 @@ let runConfig = unsuppress = []; } +let reset () = + runConfig.dce <- false; + runConfig.exception_ <- false; + runConfig.suppress <- []; + runConfig.termination <- false; + runConfig.transitive <- false; + runConfig.unsuppress <- [] + let all () = runConfig.dce <- true; runConfig.exception_ <- true; @@ -31,3 +39,24 @@ let exception_ () = runConfig.exception_ <- true let termination () = runConfig.termination <- true let transitive b = runConfig.transitive <- b + +type snapshot = { + dce: bool; + exception_: bool; + suppress: string list; + termination: bool; + transitive: bool; + unsuppress: string list; +} + +let snapshot () = + { + dce = runConfig.dce; + exception_ = runConfig.exception_; + suppress = runConfig.suppress; + termination = runConfig.termination; + transitive = runConfig.transitive; + unsuppress = runConfig.unsuppress; + } + +let equal_snapshot (a : snapshot) (b : snapshot) = a = b diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/test-reactive-server.sh b/tests/analysis_tests/tests-reanalyze/deadcode/test-reactive-server.sh index 78ca46a877..d9e420bd2b 100755 --- a/tests/analysis_tests/tests-reanalyze/deadcode/test-reactive-server.sh +++ b/tests/analysis_tests/tests-reanalyze/deadcode/test-reactive-server.sh @@ -58,7 +58,9 @@ time_end() { } BACKUP_FILE="/tmp/reactive-test-backup.$$" +CONFIG_BACKUP_FILE="/tmp/reactive-test-config-backup.$$" DEFAULT_SOCKET_FILE="" +CONFIG_FILE="" # Cleanup function cleanup() { @@ -67,6 +69,11 @@ cleanup() { cp "$BACKUP_FILE" "$TEST_FILE" rm -f "$BACKUP_FILE" fi + # Restore config file if backup exists + if [[ -n "$CONFIG_FILE" && -f "$CONFIG_BACKUP_FILE" ]]; then + cp "$CONFIG_BACKUP_FILE" "$CONFIG_FILE" + rm -f "$CONFIG_BACKUP_FILE" + fi # Stop server if running if [[ -n "$SERVER_PID" ]] && kill -0 "$SERVER_PID" 2>/dev/null; then kill "$SERVER_PID" 2>/dev/null || true @@ -190,6 +197,12 @@ configure_project() { log_error "Could not find test file for project: $project_name" exit 1 fi + + CONFIG_FILE="$PROJECT_DIR/rescript.json" + if [[ ! -f "$CONFIG_FILE" ]]; then + log_error "Could not find config file: $CONFIG_FILE" + exit 1 + fi } configure_project @@ -214,6 +227,7 @@ time_end "initial_build" # Backup the test file cp "$TEST_FILE" "$BACKUP_FILE" +cp "$CONFIG_FILE" "$CONFIG_BACKUP_FILE" # Start the server start_server() { @@ -332,6 +346,24 @@ PY time_end "json_compare" } +set_config_suppress_all() { + python3 - <