Skip to content

(complete, intrusive fix) async rules crash under the TransparentCompiler#863

Open
michaelglass wants to merge 1 commit into
fsprojects:masterfrom
michaelglass:fix/transparentcompiler-recover-projectfilename
Open

(complete, intrusive fix) async rules crash under the TransparentCompiler#863
michaelglass wants to merge 1 commit into
fsprojects:masterfrom
michaelglass:fix/transparentcompiler-recover-projectfilename

Conversation

@michaelglass

@michaelglass michaelglass commented Jul 3, 2026

Copy link
Copy Markdown

COMPLETE of two alternative fixes for the same crash — the SIMPLER alternative is #862, a smaller no-API-change fix that just degrades those rules under the TransparentCompiler. Mutually exclusive; pick one.

When this happens

If you run FSharpLint as a library — the lintParsedFile / lintParsedSource API, passing your own ProjectCheckResults — and those results come from an FSharpChecker created with useTransparentCompiler = true, linting aborts on otherwise-valid files with an internal error. The trigger is the async/library-heuristic rules: AsynchronousFunctionNames, SimpleAsyncComplementaryHelpers, and NoAsyncRunSynchronouslyInLibrary (the first and last are on by default).

Concretely: in my project FsHotWatch I use FSharpLint as a library (not the CLI tool), reusing warm FCS check results to re-lint files on save. When I switched the underlying FSharpChecker to the TransparentCompiler, valid files started failing to lint with an internal error.

Why

Those rules need only one thing: the project file name, for a name-based "is this a library?" check. They get it through the two-phase ProjectOptions lazy, which reads FSharpProjectContext.ProjectOptions — a getter that throws by design under the TransparentCompiler. And under the TransparentCompiler there's no way to recover the name from the check results at all: the context exposes no file name and no snapshot.

Fix

Let the caller supply the project file name directly, since it's all the rules need:

  • the rule-context field becomes ProjectFileName (was the whole ProjectOptions);
  • ParsedFileInformation / FileParseInfo gain a ProjectFileName: string option;
  • project-mode lint fills it from the FSharpProjectOptions it already builds; a library caller passes its .fsproj path;
  • deriving it from the check results stays as a guarded fallback, so nothing regresses when the name isn't supplied.

Net effect: the library heuristic keeps working under the TransparentCompiler instead of silently treating every project as not-a-library.

Test

TestApi.fs:

  • lints TransparentCompiler-produced results without an internal failure;
  • verifies a caller-supplied library project name (MyLib.fsproj) engages the async-naming rule under the TransparentCompiler, while None degrades to not-a-library.

Trade-off vs #862 (the SIMPLER fix)

#862 is smaller (no public API change) but leaves the three rules degraded under the TransparentCompiler. This one adds a small public field (ParsedFileInformation.ProjectFileName) plus the rule-context change, and in exchange loses no behavior.

if it wasn't obvious from the tone of the PR: this work was done with the extensive help with AI, and then eyeballed / confirmed by a fleshy human being

… TransparentCompiler

The library-heuristic rules (AsynchronousFunctionNames, SimpleAsyncComplementaryHelpers,
NoAsyncRunSynchronouslyInLibrary) need only the project file name. The two-phase
ProjectOptions lazy derived it via FSharpProjectContext.ProjectOptions, which throws by
design when the check results come from FCS's TransparentCompiler, aborting linting of
the whole file.

Expose the project file name to rules directly (AstNodeRuleParams.ProjectFileName) and
let callers supply it (ParsedFileInformation.ProjectFileName / FileParseInfo.ProjectFileName).
Project-mode lint fills it from the FSharpProjectOptions it already builds; analyzer hosts
can supply their .fsproj path. Deriving it from the check results remains as a guarded
fallback, so behavior is preserved everywhere and no longer depends on a getter the
TransparentCompiler cannot serve.

Regression: TestApi lints TransparentCompiler-produced results without an internal failure,
and verifies a caller-supplied library project name engages the async-naming rule under the
TransparentCompiler.
@michaelglass michaelglass force-pushed the fix/transparentcompiler-recover-projectfilename branch from 864cf99 to 5d80961 Compare July 3, 2026 14:47
@michaelglass michaelglass changed the title feat(Core): async rules crash under the TransparentCompiler — COMPLETE fix (preserve behavior) (complete, intrusive fix) async rules crash under the TransparentCompiler Jul 3, 2026
@Numpsy

Numpsy commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

fwiw, the F# Analyzers SDK tries to cover up some of the differences between the BackgroundCompiler and TransparentCompiler by wrapping the FSharpProjectOptions and FSharpProjectSnapshot types in a wrapper that adds a consistent API for getting information like the project file names -

https://github.com/ionide/FSharp.Analyzers.SDK/blob/f323144f0a4db51be564a3187838f2328f0e9182/src/FSharp.Analyzers.SDK/FSharp.Analyzers.SDK.fsi#L66

Not sure if doing something like that around here could add more support for the transparent compiler in general?

@knocte

knocte commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Hey @michaelglass thanks for the PRs.

Not sure if doing something like that around here could add more support for the transparent compiler in general?

Hey Richard, would adopting this approach mean that we don't need the try-with block anymore? (I'm not very fond of typeless try-with blocks)

BTW apologies in advance for not being very responsive these days, as I'm kinda busy IRL with a move.

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.

3 participants