Skip to content

Check executed F# cells as FSI runs them, each in a fragment of its own - #129

Merged
TorreyBetts merged 1 commit into
DataficationSDK:mainfrom
Jonathan-Schaefer-git:fsharp-cells-as-fragments
Oct 23, 2026
Merged

TorreyBetts merged 1 commit into
DataficationSDK:mainfrom
Jonathan-Schaefer-git:fsharp-cells-as-fragments

Conversation

@Jonathan-Schaefer-git

Copy link
Copy Markdown
Contributor

Fixes #128.

The F# kernel checks a cell for diagnostics, hover and completions in one virtual script that holds every executed cell followed by the cell being checked. Once a cell that defines a type or a module has run, its definitions appear twice in that file, and checking it again reports FS0037 on each of them.

FSI compiles each execution as a fragment of its own and opens it for the ones after. The virtual script imitates this.

Each executed cell is its own module. Each executed source is wrapped in a module of its own, followed by an open of that module. The cell being checked stays at the top level, so its positions are unchanged.
Directives still apply to the whole script. #r, #load, #I, #nowarn and the other script directives are lifted out of the module, since they apply to the whole script.
Opens stay open. A fragment’s top-level opens are repeated after it, since in FSI they stay open for later cells.
The wrappers stay invisible. Completions leave the wrapper modules out, and hover text and diagnostic messages drop their names, so a type reads as Notebook.Shape rather than Notebook.VersoExecutedCell1.Shape.

ExecutedCellsTests covers the cases:

a run cell checked again has no duplicate definitions;
a value redefined by a later cell takes the later definition;
opens persist, including the open of a module from the same cell, and a module from a run cell can be opened later;
an #r reference still resolves in later cells;
a cell with a triple-quoted string still checks;
the wrapper modules stay out of completions, hover and diagnostics.

The F# kernel checks a cell for diagnostics, hover and completions in one virtual script that
holds every executed cell followed by the cell being edited. FSI compiles each execution as a
fragment of its own and opens it for the ones after, so a later execution may define again a
type or module an earlier one defined, as a cell does each time it is run again. In one file
that is a duplicate definition: once a cell defining a type or module has run, checking it
again reports FS0037 on each of its definitions, and the editor shows those as errors.

Each executed source is now wrapped in a module of its own, which is then opened, the way FSI
does it. Script directives (#r, #load, #I, #nowarn, ...) are lifted out of the module, since
they apply to the whole script, and a fragment's top-level opens are repeated after it, since
in FSI they stay open for later cells. The cell being edited stays at the top level, so its
positions are unchanged.

The wrapper modules are kept out of what a user sees: completions leave them out, and hover
text and diagnostic messages drop their names, so a type reads as Notebook.Shape rather than
Notebook.VersoExecutedCell1.Shape.

Signed-off-by: Jonathan Schäfer <jonathan.schaefer.mail@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@TorreyBetts TorreyBetts left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for this, looks good. Will be included in the next release.

@TorreyBetts
TorreyBetts merged commit 426442b into DataficationSDK:main Oct 23, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: F# cell reports FS0037 "Duplicate definition" on its own types and modules after a run

2 participants