Detect recursive inline bindings and emit FS3888 diagnostic#19803
Detect recursive inline bindings and emit FS3888 diagnostic#19803T-Gro wants to merge 5 commits into
Conversation
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>
❗ Release notes requiredCaution 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:
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
|
T-Gro
left a comment
There was a problem hiding this comment.
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
-
CheckDeclarations.fs – the
ResizeArray+List.ofSeqpattern 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. -
Performance –
freeInExpr CollectLocalsNoCaching eis called per inline binding. Fine in practice (recursive inline groups are small), but worth a brief comment noting the assumption. -
typecheck vs compile – the test
Non-recursive inline in rec group compiles cleanlyandInline CE builder members do not trigger recursive diagnosticonly need type-checking. Usingtypecheckinstead ofcompilewould 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
ℹ️ Comment says BFS but code is DFS (correct either way for this purpose)
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
FSComp.txtplus all localization xlf files.TcLetrecBindings(CheckDeclarations.fs): after collectinglet recbindings, any binding markedinlinethat participates in a recursive cycle now triggers FS3888 at type-check time.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: addedSetInlineInfomutator onValto allow clearing the inline flag after the diagnostic is issued (so downstream phases don't crash).RecursiveSafetyAnalysis.fs): new test cases for recursive inlinelet recbindings and recursive inline members, verifying the diagnostic text and error code.