From e05908ddf14852a90658a327464464bcbffc1f9d Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sun, 22 Feb 2026 15:32:38 +0500 Subject: [PATCH 1/5] Fix bug [#19345]: rearrange the LetOrUse patterns into proper order. Add tests. --- .../Checking/Expressions/CheckExpressions.fs | 6 ++-- .../ComputationExpressions.fs | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index be76ee77ac5..b94733741d4 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -6002,6 +6002,9 @@ and TcExprUndelayed (cenv: cenv) (overallTy: OverallTy) env tpenv (synExpr: SynE CallExprHasTypeSink cenv.tcSink (m, env.NameEnv, overallTy.Commit, env.eAccessRights) cenv.TcArrayOrListComputedExpression cenv env overallTy tpenv (isArray, comp) m + | LetOrUse({ Bindings = [ SynBinding(trivia = { LeadingKeyword = leadingKeyword }) ]}, true, _) -> + error(Error(FSComp.SR.tcConstructRequiresComputationExpression(), leadingKeyword.Range)) + | SynExpr.LetOrUse _ -> TcLinearExprs (TcExprThatCanBeCtorBody cenv) cenv env overallTy tpenv false synExpr id @@ -6110,9 +6113,6 @@ and TcExprUndelayed (cenv: cenv) (overallTy: OverallTy) env tpenv (synExpr: SynE | SynExpr.MatchBang (trivia = { MatchBangKeyword = m }) | SynExpr.WhileBang (range = m) -> error(Error(FSComp.SR.tcConstructRequiresComputationExpression(), m)) - | LetOrUse({ Bindings = [ SynBinding(trivia = { LeadingKeyword = leadingKeyword }) ]}, true, _) -> - error(Error(FSComp.SR.tcConstructRequiresComputationExpression(), leadingKeyword.Range)) - | SynExpr.IndexFromEnd (rightExpr, m) -> errorR(Error(FSComp.SR.tcTraitInvocationShouldUseTick(), m)) let adjustedExpr = ParseHelpers.adjustHatPrefixToTyparLookup m rightExpr diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs index 8e340726963..040d755cb66 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs @@ -261,3 +261,32 @@ module EmptyBodied = |> shouldFail |> withErrorCode 789 |> withErrorMessage "'{ }' is not a valid expression. Records must include at least one field. Empty sequences are specified by using Seq.empty or an empty list '[]'." + +module LetUseBangTests = + + [] + let ``let! isn't allowed outside of Computation Expression`` () = + FSharp """ + let test = + let! a = 1 + 1 + () + """ + |> asExe + |> compile + |> withErrorCode 750 + |> withErrorMessage "This construct may only be used within computation expressions" + + [] + let ``use! isn't allowed outside of Computation Expression`` () = + FSharp """ + let test = + use! a = + { new IDisposable with + member this.Dispose() = () + } + () + """ + |> asExe + |> compile + |> withErrorCode 750 + |> withErrorMessage "This construct may only be used within computation expressions" \ No newline at end of file From ffc0a6d5ce31bb032eddf06f819883c3742f4d11 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sun, 22 Feb 2026 15:59:43 +0500 Subject: [PATCH 2/5] Add an release-note entry --- docs/release-notes/.FSharp.Compiler.Service/9.0.200.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/9.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/9.0.200.md index 5fd7be2081e..409b5851e97 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/9.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/9.0.200.md @@ -23,6 +23,7 @@ * Shim/file system: fix leaks of the shim [PR #18144](https://github.com/dotnet/fsharp/pull/18144) * fsi: fix auto-loading of script file inside NuGet package ([PR #18177](https://github.com/dotnet/fsharp/pull/18177)) * Fix for `Obsolete` attribute warning/error not taken into account when used with a unit of measure [PR #18182](https://github.com/dotnet/fsharp/pull/18182) +* Fix a bug where `let!` and `use!` were incorrectly allowed outside computation expressions [PR #19347](https://github.com/dotnet/fsharp/pull/19347) ### Added From 3f54e1b5077f5bcb6cdabdeb48462fb3859c5848 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sun, 22 Feb 2026 16:03:39 +0500 Subject: [PATCH 3/5] Move the entry to proper file --- docs/release-notes/.FSharp.Compiler.Service/10.0.300.md | 1 + docs/release-notes/.FSharp.Compiler.Service/9.0.200.md | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md index ca425fb63c4..ec394e2c1cc 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md @@ -21,6 +21,7 @@ * Fix FS3356 false positive for instance extension members with same name on different types, introduced by [#18821](https://github.com/dotnet/fsharp/pull/18821). ([PR #19260](https://github.com/dotnet/fsharp/pull/19260)) * Fix graph-based type checking incorrectly resolving dependencies when the same module name is defined across multiple files in the same namespace. ([PR #19280](https://github.com/dotnet/fsharp/pull/19280)) * F# Scripts: Fix default reference paths resolving when an SDK directory is specified. ([PR #19270](https://github.com/dotnet/fsharp/pull/19270)) +* Fix a bug where `let!` and `use!` were incorrectly allowed outside computation expressions. [PR #19347](https://github.com/dotnet/fsharp/pull/19347) ### Added * FSharpType: add ImportILType ([PR #19300](https://github.com/dotnet/fsharp/pull/19300)) diff --git a/docs/release-notes/.FSharp.Compiler.Service/9.0.200.md b/docs/release-notes/.FSharp.Compiler.Service/9.0.200.md index 409b5851e97..5fd7be2081e 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/9.0.200.md +++ b/docs/release-notes/.FSharp.Compiler.Service/9.0.200.md @@ -23,7 +23,6 @@ * Shim/file system: fix leaks of the shim [PR #18144](https://github.com/dotnet/fsharp/pull/18144) * fsi: fix auto-loading of script file inside NuGet package ([PR #18177](https://github.com/dotnet/fsharp/pull/18177)) * Fix for `Obsolete` attribute warning/error not taken into account when used with a unit of measure [PR #18182](https://github.com/dotnet/fsharp/pull/18182) -* Fix a bug where `let!` and `use!` were incorrectly allowed outside computation expressions [PR #19347](https://github.com/dotnet/fsharp/pull/19347) ### Added From d0f37894bbb6dc3e830f5d35a2b49246e4e44b33 Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sun, 22 Feb 2026 17:01:45 +0500 Subject: [PATCH 4/5] Replace error with errorR. Put both LetOrUse branches in the same branch. Add one more test. --- .../Checking/Expressions/CheckExpressions.fs | 9 ++++++--- .../ComputationExpressions.fs | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index b94733741d4..937a7251658 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -6002,10 +6002,13 @@ and TcExprUndelayed (cenv: cenv) (overallTy: OverallTy) env tpenv (synExpr: SynE CallExprHasTypeSink cenv.tcSink (m, env.NameEnv, overallTy.Commit, env.eAccessRights) cenv.TcArrayOrListComputedExpression cenv env overallTy tpenv (isArray, comp) m - | LetOrUse({ Bindings = [ SynBinding(trivia = { LeadingKeyword = leadingKeyword }) ]}, true, _) -> - error(Error(FSComp.SR.tcConstructRequiresComputationExpression(), leadingKeyword.Range)) + | SynExpr.LetOrUse letOrUse -> + match letOrUse with + | { Bindings = [ SynBinding(trivia = { LeadingKeyword = leadingKeyword }) ]} + when letOrUse.IsBang -> + errorR(Error(FSComp.SR.tcConstructRequiresComputationExpression(), leadingKeyword.Range)) + | _ -> () - | SynExpr.LetOrUse _ -> TcLinearExprs (TcExprThatCanBeCtorBody cenv) cenv env overallTy tpenv false synExpr id | SynExpr.TryWith (synBodyExpr, synWithClauses, mTryToLast, spTry, spWith, trivia) -> diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs index 040d755cb66..71cebb9d71d 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs @@ -279,6 +279,8 @@ module LetUseBangTests = [] let ``use! isn't allowed outside of Computation Expression`` () = FSharp """ + open System + let test = use! a = { new IDisposable with @@ -289,4 +291,17 @@ module LetUseBangTests = |> asExe |> compile |> withErrorCode 750 - |> withErrorMessage "This construct may only be used within computation expressions" \ No newline at end of file + |> withErrorMessage "This construct may only be used within computation expressions" + + [] + let ``When let! is outside of Computation Expression, the analysis lasts`` () = + FSharp """ + let test = + let! a = 1 + 1 + let! b = 1 + 1 + () + """ + |> asExe + |> compile + |> withErrorCodes [750; 750] + |> withErrorMessages (List.replicate 2 "This construct may only be used within computation expressions") From 6f71ecdfe0b5a76afdb17a3eb455200b73f3b2dd Mon Sep 17 00:00:00 2001 From: evgTSV Date: Sun, 22 Feb 2026 21:57:56 +0500 Subject: [PATCH 5/5] Fix error for a case when let! is used together with and!. Add a new test. Fix tests. --- .../Checking/Expressions/CheckExpressions.fs | 2 +- .../ComputationExpressions.fs | 24 +++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 937a7251658..1b56e4422b6 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -6004,7 +6004,7 @@ and TcExprUndelayed (cenv: cenv) (overallTy: OverallTy) env tpenv (synExpr: SynE | SynExpr.LetOrUse letOrUse -> match letOrUse with - | { Bindings = [ SynBinding(trivia = { LeadingKeyword = leadingKeyword }) ]} + | { Bindings = SynBinding(trivia = { LeadingKeyword = leadingKeyword }) :: _ } when letOrUse.IsBang -> errorR(Error(FSComp.SR.tcConstructRequiresComputationExpression(), leadingKeyword.Range)) | _ -> () diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs index 71cebb9d71d..1aec462abf9 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs @@ -294,14 +294,30 @@ module LetUseBangTests = |> withErrorMessage "This construct may only be used within computation expressions" [] - let ``When let! is outside of Computation Expression, the analysis lasts`` () = + let ``let! with and! aren't allowed outside of Computation Expression`` () = FSharp """ let test = let! a = 1 + 1 - let! b = 1 + 1 + and! b = 1 + 1 () """ |> asExe |> compile - |> withErrorCodes [750; 750] - |> withErrorMessages (List.replicate 2 "This construct may only be used within computation expressions") + |> withErrorCode 750 + |> withErrorMessage "This construct may only be used within computation expressions" + + [] + let ``When let! is outside of Computation Expression, the analysis lasts`` () = + FSharp """ + let test = + let! a = 1 + 1 + return! 0 + """ + |> asExe + |> compile + |> withDiagnostics [ + (Error 750, Line 3, Col 13, Line 3, Col 17, + "This construct may only be used within computation expressions"); + (Error 748, Line 4, Col 13, Line 4, Col 20, + "This construct may only be used within computation expressions. To return a value from an ordinary function simply write the expression without 'return'.") + ]