Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
97 changes: 58 additions & 39 deletions analysis/reanalyze/src/ReanalyzeServer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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 = {
Expand Down Expand Up @@ -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 ->
Expand Down Expand Up @@ -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) :
Expand All @@ -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;
Expand All @@ -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)
Expand Down
29 changes: 29 additions & 0 deletions analysis/reanalyze/src/RunConfig.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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() {
Expand Down Expand Up @@ -332,6 +346,24 @@ PY
time_end "json_compare"
}

set_config_suppress_all() {
python3 - <<PY
import json

path = "$CONFIG_FILE"
with open(path) as f:
cfg = json.load(f)

reanalyze = cfg.setdefault("reanalyze", {})
reanalyze["suppress"] = ["src"]
reanalyze["unsuppress"] = []

with open(path, "w") as f:
json.dump(cfg, f, indent=2)
f.write("\\n")
PY
}

# Add an unused value to the test file (creates +1 dead code warning)
add_unused_value() {
log_verbose "Adding unused value to $TEST_FILE"
Expand Down Expand Up @@ -487,6 +519,42 @@ run_scenario_make_live() {
return 0
}

run_scenario_config_change() {
local baseline_count="$1"
local server_after="/tmp/reanalyze-after-config-server-$$.json"
local server_restored="/tmp/reanalyze-restored-config-server-$$.json"

log_edit "Updating rescript.json (reanalyze.suppress=[\"src\"])..."
set_config_suppress_all

log_reactive "Analyzing after config change..."
send_request "$server_after" "incremental"
local suppressed_count
suppressed_count=$(count_issues "$server_after")
if [[ "$suppressed_count" -ne 0 ]]; then
log_error "Expected 0 issues after suppressing src via config, got $suppressed_count"
rm -f "$server_after" "$server_restored"
return 1
fi

cp "$CONFIG_BACKUP_FILE" "$CONFIG_FILE"

log_reactive "Analyzing after config restore..."
send_request "$server_restored" "incremental"

local restored_count
restored_count=$(count_issues "$server_restored")
if [[ "$restored_count" -ne "$baseline_count" ]]; then
log_error "Expected $baseline_count issues after restoring config, got $restored_count"
rm -f "$server_after" "$server_restored"
return 1
fi

log "✓ Config change invalidates/recomputes server cache correctly"
rm -f "$server_after" "$server_restored"
return 0
}

# Run one benchmark iteration
run_iteration() {
local iter="$1"
Expand Down Expand Up @@ -539,6 +607,14 @@ main() {
baseline_count=$(count_issues "$baseline_file")
log "Baseline: $baseline_count issues"
log ""

if ! run_scenario_config_change "$baseline_count"; then
log_error "Config-change scenario failed"
stop_server
rm -f "$baseline_file"
exit 1
fi
log ""

#-----------------------------------------
# BENCHMARK PHASE: Edit → Rebuild → Measure
Expand Down Expand Up @@ -627,4 +703,3 @@ main() {
}

main

Loading