diff --git a/src/public.jl b/src/public.jl index 13f0963..2fa3908 100644 --- a/src/public.jl +++ b/src/public.jl @@ -263,9 +263,20 @@ function remove_file!(jw::JuliaWorkspace, uri::URI) process_from_dynamic(jw) _remove_file!(jw, uri) + _evict_derived_for_uris!(jw, (uri,)) _reconcile!(jw) end +# Evict all memoized derived values keyed by any of the given (removed) URIs. +# Without this they would stay in the Salsa storage for the lifetime of the +# workspace, since nothing queries a removed URI again. +function _evict_derived_for_uris!(jw::JuliaWorkspace, uris) + uri_set = Set{URI}(uris) + Salsa.evict_derived!(jw.runtime) do key + any(arg -> arg isa URI && arg in uri_set, key.args) + end +end + # Input-only removal. Does not drain results or reconcile. function _remove_file!(jw::JuliaWorkspace, uri::URI) files = input_files(jw.runtime) @@ -293,14 +304,18 @@ function remove_all_children!(jw::JuliaWorkspace, uri::URI) uri_as_string = string(uri) + removed = URI[] for file in files file_as_string = string(file) if startswith(file_as_string, uri_as_string) _remove_file!(jw, file) + push!(removed, file) end end + isempty(removed) || _evict_derived_for_uris!(jw, removed) + _reconcile!(jw) end @@ -346,6 +361,7 @@ function clear_indirect_file!(jw::JuliaWorkspace, uri::URI) process_from_dynamic(jw) _clear_indirect_tracking!(jw, uri) + _evict_derived_for_uris!(jw, (uri,)) _reconcile!(jw) end diff --git a/test/test_eviction.jl b/test/test_eviction.jl new file mode 100644 index 0000000..796133d --- /dev/null +++ b/test/test_eviction.jl @@ -0,0 +1,55 @@ +@testitem "remove_file! evicts derived values for the removed URI" begin + using JuliaWorkspaces.URIs2: URI + using JuliaWorkspaces: Salsa + + n_entries_for(jw, uri) = + Salsa.evict_derived!(key -> any(arg -> isequal(arg, uri), key.args), jw.runtime) + + uri1 = URI("file:///evicttest/src/a.jl") + uri2 = URI("file:///evicttest/src/b.jl") + + jw = JuliaWorkspace() + add_file!(jw, TextFile(uri1, SourceText("f(x) = x + 1", "julia"))) + add_file!(jw, TextFile(uri2, SourceText("g(x) = x - 1", "julia"))) + + # Populate the derived caches. + get_diagnostic(jw, uri1) + get_diagnostic(jw, uri2) + + remove_file!(jw, uri1) + + # No derived entry may still be keyed by the removed URI. (The probe uses + # evict_derived! itself: it returns the number of matching entries.) + @test n_entries_for(jw, uri1) == 0 + + # The surviving file is untouched and still queryable. + @test get_diagnostic(jw, uri2) isa Vector + @test has_file(jw, uri2) +end + +@testitem "remove_all_children! evicts derived values for all removed URIs" begin + using JuliaWorkspaces.URIs2: URI + using JuliaWorkspaces: Salsa + + n_entries_for(jw, uri) = + Salsa.evict_derived!(key -> any(arg -> isequal(arg, uri), key.args), jw.runtime) + + uris = [URI("file:///evictfolder/src/f$i.jl") for i in 1:5] + outside = URI("file:///other/src/g.jl") + + jw = JuliaWorkspace() + for uri in uris + add_file!(jw, TextFile(uri, SourceText("h(x) = x", "julia"))) + end + add_file!(jw, TextFile(outside, SourceText("h(x) = x", "julia"))) + + get_diagnostics(jw) + + remove_all_children!(jw, URI("file:///evictfolder")) + + for uri in uris + @test n_entries_for(jw, uri) == 0 + end + @test has_file(jw, outside) + @test get_diagnostic(jw, outside) isa Vector +end