Skip to content

Recover from attached_packages cycles in R/ collation - #1393

Merged
juliasilge merged 2 commits into
mainfrom
bugfix/attached-packages-salsa-cycle
Aug 24, 2026
Merged

Recover from attached_packages cycles in R/ collation#1393
juliasilge merged 2 commits into
mainfrom
bugfix/attached-packages-salsa-cycle

Conversation

@juliasilge

Copy link
Copy Markdown
Member

Intent

Fixes a panic that killed the LSP main loop for a common project layout: a script in an R/ directory that calls source() on one of its collation siblings.

Addresses posit-dev/positron#15631.

Approach

Salsa re-entered File::attached_packages, found no recovery on it, and aborted with dependency graph cycle when querying File::attached_packages.

The cycle comes from R/ collation, not from mutual source(). Building a file's semantic index reads the attached_packages of each collation predecessor, and building that predecessor's index resolves its own source() sites, which asks for the first file's attaches again. Three loose scripts are enough, where a.R sources b.R and nothing sources back:

semantic_index(c)
  -> cross_file_layers(c)             predecessors a, b
    -> attached_packages(a)           1st entry
      -> semantic_index(a)
        -> resolves `source("R/b.R")`
          -> semantic_index(b)
            -> cross_file_layers(b)   predecessor a
              -> attached_packages(a) 2nd entry, cycle

Which query salsa re-enters depends on which file the editor touches first. semantic_index and exports already carry cycle_result recovery, so the orderings that re-enter at either of those degrade instead of crashing. The orderings that re-enter at attached_packages had nothing to fall back to.

attached_packages and attached_packages_anywhere now carry cycle_result recovery as well. A file caught in the cycle contributes no attaches for the revision, so the LSP degrades instead of crashing. The recovery on attached_packages_anywhere is defensive and unreachable today, and its doc comment says so.

This recovery is deliberately not the whole fix. The cycle is a false one, and the files in it already lose their attaches inside the NoopImportsResolver rebuild that the semantic_index recovery performs. Restoring those attaches means breaking the cycle structurally, which is a design change rather than a patch. I will open follow-up issues for that work and for the other cleanups this turned up.

Testing

Adds test_r_directory_collation_with_a_source_call_does_not_panic, which builds the three-script layout above and queries c.R first, since that ordering is what makes salsa re-enter attached_packages. The test fails with the recovery removed.

The test asserts the degraded dependency set (["pkgc"]) rather than the correct one. That is current behaviour, not intended behaviour, and the comment above the assertion explains the difference. When the follow-up lands, this assertion flips to all three packages.

A fuzz pass over 400 random R/ directory shapes with random source() edges, across every entry point and target file, went from 3948 panics before the change to 0 after.

Positron Release Notes

New Features

  • N/A

Bug Fixes

  • Fixes a crash in the R language server for projects with an R/ directory where one script calls source() on another (#15631).

@thomasp85 thomasp85 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - nice and surgical

@juliasilge
juliasilge merged commit 5564f48 into main Aug 24, 2026
17 checks passed
@juliasilge
juliasilge deleted the bugfix/attached-packages-salsa-cycle branch August 24, 2026 13:41
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 24, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants