Skip to content

Detect recursive inline bindings and emit FS3888 diagnostic#19803

Open
T-Gro wants to merge 5 commits into
mainfrom
fix/issue-17991
Open

Detect recursive inline bindings and emit FS3888 diagnostic#19803
T-Gro wants to merge 5 commits into
mainfrom
fix/issue-17991

Conversation

@T-Gro
Copy link
Copy Markdown
Member

@T-Gro T-Gro commented May 25, 2026

Fixes #17991

Summary

Recursive use of an inline function or member now produces a clear error (FS3888: The value '{0}' is declared inline but is used recursively. Recursive inline definitions are not supported.) instead of the misleading FS1113/FS1114 errors that appeared previously.

Changes

  • New diagnostic FS3888 added to FSComp.txt plus all localization xlf files.
  • Detection in TcLetrecBindings (CheckDeclarations.fs): after collecting let rec bindings, any binding marked inline that participates in a recursive cycle now triggers FS3888 at type-check time.
  • Detection in TcMutRecBindings_Phase2C (CheckExpressions.fs): extends the same check to class members via cycle detection through inline-only edges, avoiding false positives on non-recursive inline property accessors.
  • TypedTree.fs/.fsi: added SetInlineInfo mutator on Val to allow clearing the inline flag after the diagnostic is issued (so downstream phases don't crash).
  • Tests (RecursiveSafetyAnalysis.fs): new test cases for recursive inline let rec bindings and recursive inline members, verifying the diagnostic text and error code.

T-Gro and others added 4 commits May 25, 2026 12:33
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ostic

Extend recursive-inline detection to class members via TcMutRecBindings_Phase2C,
using cycle detection through inline bindings only so non-recursive inline
member accesses (e.g. inline get/set referencing other plain members) are not
false-positives.

Refs #17991

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

❗ Release notes required

@T-Gro,

Caution

No release notes found for the changed paths (see table below).

Please make sure to add an entry with an informative description of the change as well as link to this pull request, issue and language suggestion if applicable. Release notes for this repository are based on Keep A Changelog format.

The following format is recommended for this repository:

* <Informative description>. ([PR #XXXXX](https://github.com/dotnet/fsharp/pull/XXXXX))

See examples in the files, listed in the table below or in th full documentation at https://fsharp.github.io/fsharp-compiler-docs/release-notes/About.html.

If you believe that release notes are not necessary for this PR, please add NO_RELEASE_NOTES label to the pull request.

You can open this PR in browser to add release notes: open in github.dev

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/11.0.100.md No release notes found or release notes format is not correct

@github-actions github-actions Bot added the AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed label May 25, 2026
Copy link
Copy Markdown
Member Author

@T-Gro T-Gro left a comment

Choose a reason for hiding this comment

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

Review: Detect recursive inline bindings and emit FS3888 diagnostic

Overall this is a well-designed fix that replaces the confusing cascade of FS1113/FS1114/FS1116/FS1118 with a single clear diagnostic. The error-recovery pattern (emit FS3888, then downgrade to ValInline.Never) is sound and prevents downstream cascading.

Correctness

The cycle-detection algorithm is correct. One minor nit: the comment says "perform BFS" but the implementation uses fv :: queue (prepend) with List.head/List.tail (pop front), which is actually DFS (stack behavior). The result is still correct for cycle detection; only the comment is inaccurate.

Tests

The new tests use raw diagnostic access (r.Output.Diagnostics) with Assert.Equal/Assert.Contains instead of the established component test DSL (shouldFail |> withDiagnostics [...]). Per the project's component-test instructions, the pipeline style is preferred. Consider rewriting, e.g.:

FSharp "module M\nlet rec inline f x = f x"
|> typecheck
|> shouldFail
|> withDiagnostics [
    (Error 3888, Line 2, Col 16, Line 2, Col 17,
     "The value or member 'f' has been marked 'inline' but is part of a recursive binding group...")
]

Also: the Recursive inline member emits single clear diagnostic test asserts exactly 1 diagnostic. If in the future additional warnings are emitted for the same code, this will break. Consider withErrorCode 3888 or filtering by error code for robustness.

Minor suggestions

  1. CheckDeclarations.fs – the ResizeArray + List.ofSeq pattern is fine, but you could avoid the allocation by using a plain F# list accumulator reversed at the end, keeping the style more consistent with the surrounding functional code.

  2. PerformancefreeInExpr CollectLocalsNoCaching e is called per inline binding. Fine in practice (recursive inline groups are small), but worth a brief comment noting the assumption.

  3. typecheck vs compile – the test Non-recursive inline in rec group compiles cleanly and Inline CE builder members do not trigger recursive diagnostic only need type-checking. Using typecheck instead of compile would be faster on CI per the test guidelines.

Summary

✅ Algorithmic correctness looks good
✅ Good error recovery via SetInlineInfo
✅ Covers both module-level and class-member paths
⚠️ Test style diverges from DSL conventions
ℹ️ Comment says BFS but code is DFS (correct either way for this purpose)

@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-reviewed PR reviewed by AI review council AI-Tooling-Check-Bypassed Tooling check: non-fork PR, not diff-analyzed

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

Recursive use of inline member should produce better diagnostic

1 participant