From 8a10a4fdaf8e6257f5c5c0868479a9be655519d3 Mon Sep 17 00:00:00 2001 From: Edgar Gonzalez Date: Sun, 20 Apr 2025 17:44:56 +0100 Subject: [PATCH 01/10] Allow `_` in `use!` bindings values --- .../.FSharp.Compiler.Service/9.0.300.md | 1 + docs/release-notes/.Language/preview.md | 1 + .../CheckComputationExpressions.fs | 26 +- src/Compiler/FSComp.txt | 1 + src/Compiler/Facilities/LanguageFeatures.fs | 3 + src/Compiler/Facilities/LanguageFeatures.fsi | 1 + src/Compiler/pars.fsy | 22 +- src/Compiler/xlf/FSComp.txt.cs.xlf | 5 + src/Compiler/xlf/FSComp.txt.de.xlf | 5 + src/Compiler/xlf/FSComp.txt.es.xlf | 5 + src/Compiler/xlf/FSComp.txt.fr.xlf | 5 + src/Compiler/xlf/FSComp.txt.it.xlf | 5 + src/Compiler/xlf/FSComp.txt.ja.xlf | 5 + src/Compiler/xlf/FSComp.txt.ko.xlf | 5 + src/Compiler/xlf/FSComp.txt.pl.xlf | 5 + src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 5 + src/Compiler/xlf/FSComp.txt.ru.xlf | 5 + src/Compiler/xlf/FSComp.txt.tr.xlf | 5 + src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 5 + src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 5 + .../FSharp.Compiler.ComponentTests.fsproj | 1 + .../Language/UseBangBindingsTests.fs | 336 ++++++++++++++++++ 22 files changed, 447 insertions(+), 10 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs diff --git a/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md index 16f5721e22a..5e0a2bc6139 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md @@ -25,6 +25,7 @@ * Fixed [#18433](https://github.com/dotnet/fsharp/issues/18433), a rare case of an internal error in xml comment processing. ([PR #18436](https://github.com/dotnet/fsharp/pull/18436)) * Fix confusing type inference error in task expression ([Issue #13789](https://github.com/dotnet/fsharp/issues/13789), [PR #18450](https://github.com/dotnet/fsharp/pull/18450)) * Fix missing `null` highlighting in tooltips ([PR #18457](https://github.com/dotnet/fsharp/pull/18457)) +* Allow `_` in `use!` bindings values (lift FS1228 restriction) ([PR #18189](https://github.com/dotnet/fsharp/pull/18189)) * Make `[]` combination work([PR #18444](https://github.com/dotnet/fsharp/pull/18444/)) ### Added diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index 905e086a163..3ebcf87e745 100644 --- a/docs/release-notes/.Language/preview.md +++ b/docs/release-notes/.Language/preview.md @@ -4,6 +4,7 @@ * Deprecate places where `seq` can be omitted. ([Language suggestion #1033](https://github.com/fsharp/fslang-suggestions/issues/1033), [PR #17772](https://github.com/dotnet/fsharp/pull/17772)) * Added type conversions cache, only enabled for compiler runs ([PR#17668](https://github.com/dotnet/fsharp/pull/17668)) * Support ValueOption + Struct attribute as optional parameter for methods ([Language suggestion #1136](https://github.com/fsharp/fslang-suggestions/issues/1136), [PR #18098](https://github.com/dotnet/fsharp/pull/18098)) +* Allow `_` in `use!` bindings values (lift FS1228 restriction) ([PR #18189](https://github.com/dotnet/fsharp/pull/18189)) * Warn when `unit` is passed to an `obj`-typed argument ([PR #18330](https://github.com/dotnet/fsharp/pull/18330)) ### Fixed diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index a7bcc1f9f87..954a41c9a8a 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -4,6 +4,7 @@ /// with generalization at appropriate points. module internal FSharp.Compiler.CheckComputationExpressions +open FSharp.Compiler.TcGlobals open Internal.Utilities.Library open FSharp.Compiler.AccessibilityLogic open FSharp.Compiler.AttributeChecking @@ -1809,17 +1810,29 @@ let rec TryTranslateComputationExpression translatedCtxt ) - // 'use! pat = e1 in e2' --> build.Bind(e1, (function _argN -> match _argN with pat -> build.Using(x, (fun _argN -> match _argN with pat -> e2)))) + // 'use! pat = e1 in e2' --> build.Bind(e1, (function _argN -> match _argN with pat -> build.Using(x, (fun _argN -> match _argN with pat -> e2)))) + // or + // 'use! _ = e1 in e2' --> build.Bind(e1, (fun _argN -> e2)) | ExprAsUseBang(spBind, isFromSource, pat, rhsExpr, andBangs, innerComp, mBind) -> if ceenv.isQuery then error (Error(FSComp.SR.tcBindMayNotBeUsedInQueries (), mBind)) - match pat, andBangs with - | (SynPat.Named(ident = SynIdent(id, _); isThisVal = false) | SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ]))), [] -> + match andBangs with + | [] -> // Valid pattern case - handle with Using + Bind requireBuilderMethod "Using" mBind cenv ceenv.env ceenv.ad ceenv.builderTy mBind requireBuilderMethod "Bind" mBind cenv ceenv.env ceenv.ad ceenv.builderTy mBind + let ident, pat = + match pat with + | SynPat.Named(ident = SynIdent(id, _); isThisVal = false) -> id, pat + | SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ])) -> id, pat + | SynPat.Wild(m) when ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.UseBangBindingValueDiscard -> + // Special handling for wildcard (_) patterns + let tmpIdent = mkSynId m "_" + tmpIdent, SynPat.Named(SynIdent(tmpIdent, None), false, None, m) + | _ -> error (Error(FSComp.SR.tcInvalidUseBangBinding (), pat.Range)) + let bindExpr = let consumeExpr = SynExpr.MatchLambda( @@ -1840,14 +1853,14 @@ let rec TryTranslateComputationExpression ) let consumeExpr = - mkSynCall "Using" mBind [ SynExpr.Ident id; consumeExpr ] ceenv.builderValName + mkSynCall "Using" mBind [ SynExpr.Ident ident; consumeExpr ] ceenv.builderValName let consumeExpr = SynExpr.MatchLambda( false, mBind, [ - SynMatchClause(pat, None, consumeExpr, id.idRange, DebugPointAtTarget.No, SynMatchClauseTrivia.Zero) + SynMatchClause(pat, None, consumeExpr, ident.idRange, DebugPointAtTarget.No, SynMatchClauseTrivia.Zero) ], DebugPointAtBinding.NoneAtInvisible, mBind @@ -1860,8 +1873,7 @@ let rec TryTranslateComputationExpression |> addBindDebugPoint spBind Some(translatedCtxt bindExpr) - | _pat, [] -> error (Error(FSComp.SR.tcInvalidUseBangBinding (), mBind)) - | _pat, _ands -> + | _ -> // Has andBangs let m = match andBangs with diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 9bfa12ce963..d1dad954b3b 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1798,3 +1798,4 @@ featureDontWarnOnUppercaseIdentifiersInBindingPatterns,"Don't warn on uppercase featureDeprecatePlacesWhereSeqCanBeOmitted,"Deprecate places where 'seq' can be omitted" featureSupportValueOptionsAsOptionalParameters,"Support ValueOption as valid type for optional member parameters" featureSupportWarnWhenUnitPassedToObjArg,"Warn when unit is passed to a member accepting `obj` argument, e.g. `Method(o:obj)` will warn if called via `Method()`." +featureUseBangBindingValueDiscard,"Use bang binding with value discard" diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 7a9a14b8602..874b709d658 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -99,6 +99,7 @@ type LanguageFeature = | DeprecatePlacesWhereSeqCanBeOmitted | SupportValueOptionsAsOptionalParameters | WarnWhenUnitPassedToObjArg + | UseBangBindingValueDiscard /// LanguageVersion management type LanguageVersion(versionText) = @@ -229,6 +230,7 @@ type LanguageVersion(versionText) = LanguageFeature.DeprecatePlacesWhereSeqCanBeOmitted, previewVersion LanguageFeature.SupportValueOptionsAsOptionalParameters, previewVersion LanguageFeature.WarnWhenUnitPassedToObjArg, previewVersion + LanguageFeature.UseBangBindingValueDiscard, previewVersion ] static let defaultLanguageVersion = LanguageVersion("default") @@ -391,6 +393,7 @@ type LanguageVersion(versionText) = | LanguageFeature.DeprecatePlacesWhereSeqCanBeOmitted -> FSComp.SR.featureDeprecatePlacesWhereSeqCanBeOmitted () | LanguageFeature.SupportValueOptionsAsOptionalParameters -> FSComp.SR.featureSupportValueOptionsAsOptionalParameters () | LanguageFeature.WarnWhenUnitPassedToObjArg -> FSComp.SR.featureSupportWarnWhenUnitPassedToObjArg () + | LanguageFeature.UseBangBindingValueDiscard -> FSComp.SR.featureUseBangBindingValueDiscard () /// Get a version string associated with the given feature. static member GetFeatureVersionString feature = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index 410a8b193c9..f50357f3fe0 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -90,6 +90,7 @@ type LanguageFeature = | DeprecatePlacesWhereSeqCanBeOmitted | SupportValueOptionsAsOptionalParameters | WarnWhenUnitPassedToObjArg + | UseBangBindingValueDiscard /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy index 7cac0ad0dc0..e6e3a657592 100644 --- a/src/Compiler/pars.fsy +++ b/src/Compiler/pars.fsy @@ -3755,13 +3755,23 @@ atomicPattern: { SynPat.ArrayOrList(true, $2, lhs parseState) } | UNDERSCORE - { SynPat.Wild(lhs parseState) } + { (* Underscore pattern ('_') is represented as SynPat.Wild + This wild pattern is used in all binding forms: + - let _ = ... + - use _ = ... + - let! _ = ... + - use! _ = ... + This ensures consistent representation of wildcard bindings in the AST *) + SynPat.Wild(lhs parseState) } | QMARK ident { SynPat.OptionalVal($2, lhs parseState) } | atomicPatternLongIdent %prec prec_atompat_pathop - { let vis, lidwd = $1 + { (* This rule handles identifiers in patterns like 'use! __' *) + (* For simple identifiers (like '__'), it creates a SynPat.Named AST node *) + (* For complex paths (A.B.C) or uppercase ids, it calls mkSynPatMaybeVar *) + let vis, lidwd = $1 if not (isNilOrSingleton lidwd.LongIdent) || String.isLeadingIdentifierCharacterUpperCase (List.head lidwd.LongIdent).idText then mkSynPatMaybeVar lidwd vis (lhs parseState) else @@ -4431,7 +4441,13 @@ declExpr: SynExpr.YieldOrReturnFrom(($1, not $1), arbExpr ("yield!", mYieldAll), mYieldAll, trivia) } | BINDER headBindingPattern EQUALS typedSequentialExprBlock IN opt_OBLOCKSEP moreBinders typedSequentialExprBlock %prec expr_let - { let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 5) + { (* This rule handles the 'use!' and 'let!' binding expressions in computation expressions *) + (* The BINDER token represents keywords like 'use!' or 'let!' *) + (* headBindingPattern represents patterns in the binding: + - Underscore ('_') patterns are preserved as SynPat.Wild + - Named patterns ('__') are represented as SynPat.Named + - Identifiers (like 'value') are represented as SynPat.LongIdent *) + let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 5) let mEquals = rhs parseState 3 let m = unionRanges (rhs parseState 1) $8.Range let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals } diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 7d64601c303..491e8a205c2 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -657,6 +657,11 @@ Interoperabilita mezi neřízeným obecným omezením jazyka C# a F# (emitovat další modreq) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 67f4270377e..ada82f801ce 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -657,6 +657,11 @@ Interop zwischen nicht verwalteter generischer Einschränkung in C# und F# (zusätzlicher ModReq ausgeben) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 9478e3d2c55..d968092fd27 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -657,6 +657,11 @@ Interoperabilidad entre la restricción genérica no administrada de C# y F# (emitir modreq adicional) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 57fade5d250..ad43afb8a8a 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -657,6 +657,11 @@ Interopérabilité entre les contraintes génériques non gérées de C# et F# (émettre un modreq supplémentaire) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 14d670e8455..8abe6bcd59a 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -657,6 +657,11 @@ Interoperabilità tra il vincolo generico non gestito di C# e di F# (crea un modreq aggiuntivo) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 78acb0fd944..693b5af58bb 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -657,6 +657,11 @@ C# と F# のアンマネージド ジェネリック制約の間の相互運用 (追加の modreq を出力) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 604ac431e4a..cd9028b19a7 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -657,6 +657,11 @@ C#과 F#의 관리되지 않는 제네릭 제약 조건 간의 Interop(추가 modreq 내보내기) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index fdee3d82f7d..9b0d13da5da 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -657,6 +657,11 @@ Międzyoperacyjnie między niezarządzanym ograniczeniem ogólnym języka C# i F# (emituj dodatkowe modreq) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 2f86c57d960..10920378dcc 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -657,6 +657,11 @@ Interoperabilidade entre a restrição genérica não gerenciada de C# e F# (emitir modreq adicional) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index fefd5255a0b..f15fa632ad4 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -657,6 +657,11 @@ Взаимодействие между универсальным ограничением "unmanaged" C# и F#(создание дополнительного modreq) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 5325f0ab09f..9e26ccb98db 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -657,6 +657,11 @@ C# ile F#' arasında yönetilmeyen genel kısıtlama (ek modreq yayın) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 2e8b957d810..5b707c0ba2d 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -657,6 +657,11 @@ C# 和 F# 的非托管泛型约束之间的互操作(发出额外的 modreq) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index f0924b3d30f..86b0c173425 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -657,6 +657,11 @@ C# 與 F# 的非受控泛型條件約束之間的 Interop (發出額外的 modreq) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 60b867c7815..fcea528d953 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -232,6 +232,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs new file mode 100644 index 00000000000..dbff980fef8 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs @@ -0,0 +1,336 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Language + +open Xunit +open FSharp.Test.Compiler + +module UseBangBindingsVersion9Tests = + [] + let ``Error when using discard pattern in use! binding`` () = + FSharp """ +module Program +open System +type Counter(value: int) = + member _.Value = value + interface IDisposable with + member _.Dispose() = () + +let getCounterAsync initial = async { + return new Counter(initial) +} + +let exampleAsync() = async { + use! counter = getCounterAsync 5 + use! __ = getCounterAsync 4 + use! _ = getCounterAsync 3 + return () +} + +[] +let main _ = + exampleAsync() |> Async.RunSynchronously + 0 + """ + |> withLangVersion90 + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 1228, Line 16, Col 10, Line 16, Col 11, "'use!' bindings must be of the form 'use! = '") + ] + + [] + let ``Named patterns are allowed in use! binding`` () = + FSharp """ +module Program +let doSomething () = + async { + use _ = { new System.IDisposable with member _.Dispose() = printfn "disposed 1" } + use! __ = Async.OnCancel (fun () -> printfn "disposed 2") + use! res2 = Async.OnCancel (fun () -> printfn "disposed 3") + do! Async.Sleep(100) + return () + } + +[] +let main _ = + let cts = new System.Threading.CancellationTokenSource() + Async.Start(doSomething(), cts.Token) + System.Threading.Thread.Sleep(100) + cts.Cancel() + 0 + """ + |> withLangVersion90 + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "disposed 1" + "disposed 3" + "disposed 2" + ] + + [] + let ``Both named and discard patterns in custom computation expression use! binding`` () = + FSharp """ +module Program +open System +type Counter(value: int) = + member _.Value = value + + interface IDisposable with + member _.Dispose() = + printfn $"Counter with value %d{value} disposed" + +type CounterBuilder() = + member _.Using(resource: #IDisposable, f) = + async { + use res = resource + return! f res + } + + member _.Bind(counter: Counter, f) = + async { + let c = counter + return! f c + } + + member _.Return(x) = async.Return x + + member _.ReturnFrom(x) = x + + member _.Bind(task, f) = async.Bind(task, f) + +[] +let main _ = + let counterBuilder = CounterBuilder() + + let example() = + counterBuilder { + use! res = new Counter(5) + use! __ = new Counter(4) + do! Async.Sleep 1000 + return () + } + + example() + |> Async.RunSynchronously + 0 + """ + |> withLangVersion90 + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "Counter with value 4 disposed" + "Counter with value 5 disposed" + ] + +module UseBangBindingsPreviewTests = + [] + let ``Discard pattern allowed in async use! binding`` () = + FSharp """ +module Program + +open System + +type Counter(value: int) = + member _.Value = value + + interface IDisposable with + member _.Dispose() = + printfn $"Counter with value %d{value} disposed" + +let getCounterAsync initial = async { + do! Async.Sleep 100 // Simulate async work + printfn $"Created counter with value %d{initial}" + return new Counter(initial) +} + +let exampleAsync() = async { + printfn "Starting async workflow" + use! counter = getCounterAsync 5 + use! __ = getCounterAsync 4 + use! _ = getCounterAsync 3 + printfn "Done using the resource" + return () +} + +[] +let main _ = + exampleAsync() |> Async.RunSynchronously + 0 + """ + |> withLangVersionPreview + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "Starting async workflow" + "Created counter with value 5" + "Created counter with value 4" + "Created counter with value 3" + "Done using the resource" + "Counter with value 3 disposed" + "Counter with value 4 disposed" + "Counter with value 5 disposed" + ] + + [] + let ``Discard pattern allowed in async use! binding 2`` () = + FSharp """ +module Program +let doSomething () = + async { + use _ = { new System.IDisposable with member _.Dispose() = printfn "disposed 1" } + use! __ = Async.OnCancel (fun () -> printfn "disposed 2") + use! res2 = Async.OnCancel (fun () -> printfn "disposed 3") + use! _ = Async.OnCancel (fun () -> printfn "disposed 4") + do! Async.Sleep(100) + return () + } + +[] +let main _ = + let cts = new System.Threading.CancellationTokenSource() + Async.Start(doSomething(), cts.Token) + System.Threading.Thread.Sleep(100) + cts.Cancel() + 0 + """ + |> withLangVersionPreview + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "disposed 1" + "disposed 4" + "disposed 3" + "disposed 2" + ] + + [] + let ``Both named and discard patterns in custom computation expression use! binding`` () = + FSharp """ +module Program +open System +type Counter(value: int) = + member _.Value = value + + interface IDisposable with + member _.Dispose() = + printfn $"Counter with value %d{value} disposed" + +type CounterBuilder() = + member _.Using(resource: #IDisposable, f) = + async { + use res = resource + return! f res + } + + member _.Bind(counter: Counter, f) = + async { + let c = counter + return! f c + } + + member _.Return(x) = async.Return x + + member _.ReturnFrom(x) = x + + member _.Bind(task, f) = async.Bind(task, f) + +[] +let main _ = + let counterBuilder = CounterBuilder() + + let example() = + counterBuilder { + use! res = new Counter(5) + use! __ = new Counter(4) + use! _ = new Counter(3) + do! Async.Sleep 1000 + return () + } + + example() + |> Async.RunSynchronously + 0 + """ + |> withLangVersionPreview + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "Counter with value 3 disposed" + "Counter with value 4 disposed" + "Counter with value 5 disposed" + ] + + [] + let ``Discard patterns are allowed in use and use! binding`` () = + FSharp """ +module Program +open System +type Counter(value: int) = + member _.Value = value + + interface IDisposable with + member _.Dispose() = + printfn $"Counter with value %d{value} disposed" + +type CounterBuilder() = + member _.Using(resource: #IDisposable, f) = + async { + use res = resource + return! f res + } + + member _.Bind(counter: Counter, f) = + async { + let c = counter + return! f c + } + + member _.Return(x) = async.Return x + + member _.ReturnFrom(x) = x + + member _.Bind(task, f) = async.Bind(task, f) + +[] +let main _ = + let counterBuilder = CounterBuilder() + + let example() = + counterBuilder { + use _ = { new System.IDisposable with member _.Dispose() = printfn "disposed 1" } + use! _ = new Counter(2) + do! Async.Sleep 1000 + return () + } + + example() + |> Async.RunSynchronously + 0 + """ + |> withLangVersionPreview + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "Counter with value 2 disposed" + "disposed 1" + ] \ No newline at end of file From 43d6b78e4326a9c37f05517f624d7e87376744c9 Mon Sep 17 00:00:00 2001 From: Edgar Gonzalez Date: Sun, 20 Apr 2025 17:44:56 +0100 Subject: [PATCH 02/10] Allow `_` in `use!` bindings values --- .../.FSharp.Compiler.Service/9.0.300.md | 1 + docs/release-notes/.Language/preview.md | 1 + .../CheckComputationExpressions.fs | 26 +- src/Compiler/FSComp.txt | 1 + src/Compiler/Facilities/LanguageFeatures.fs | 3 + src/Compiler/Facilities/LanguageFeatures.fsi | 1 + src/Compiler/pars.fsy | 22 +- src/Compiler/xlf/FSComp.txt.cs.xlf | 5 + src/Compiler/xlf/FSComp.txt.de.xlf | 5 + src/Compiler/xlf/FSComp.txt.es.xlf | 5 + src/Compiler/xlf/FSComp.txt.fr.xlf | 5 + src/Compiler/xlf/FSComp.txt.it.xlf | 5 + src/Compiler/xlf/FSComp.txt.ja.xlf | 5 + src/Compiler/xlf/FSComp.txt.ko.xlf | 5 + src/Compiler/xlf/FSComp.txt.pl.xlf | 5 + src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 5 + src/Compiler/xlf/FSComp.txt.ru.xlf | 5 + src/Compiler/xlf/FSComp.txt.tr.xlf | 5 + src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 5 + src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 5 + .../FSharp.Compiler.ComponentTests.fsproj | 1 + .../Language/UseBangBindingsTests.fs | 336 ++++++++++++++++++ 22 files changed, 447 insertions(+), 10 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs diff --git a/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md index 16f5721e22a..7224d401d6e 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md @@ -25,6 +25,7 @@ * Fixed [#18433](https://github.com/dotnet/fsharp/issues/18433), a rare case of an internal error in xml comment processing. ([PR #18436](https://github.com/dotnet/fsharp/pull/18436)) * Fix confusing type inference error in task expression ([Issue #13789](https://github.com/dotnet/fsharp/issues/13789), [PR #18450](https://github.com/dotnet/fsharp/pull/18450)) * Fix missing `null` highlighting in tooltips ([PR #18457](https://github.com/dotnet/fsharp/pull/18457)) +* Allow `_` in `use!` bindings values (lift FS1228 restriction) ([PR #18487](https://github.com/dotnet/fsharp/pull/18487)) * Make `[]` combination work([PR #18444](https://github.com/dotnet/fsharp/pull/18444/)) ### Added diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index 905e086a163..d1576a84eca 100644 --- a/docs/release-notes/.Language/preview.md +++ b/docs/release-notes/.Language/preview.md @@ -4,6 +4,7 @@ * Deprecate places where `seq` can be omitted. ([Language suggestion #1033](https://github.com/fsharp/fslang-suggestions/issues/1033), [PR #17772](https://github.com/dotnet/fsharp/pull/17772)) * Added type conversions cache, only enabled for compiler runs ([PR#17668](https://github.com/dotnet/fsharp/pull/17668)) * Support ValueOption + Struct attribute as optional parameter for methods ([Language suggestion #1136](https://github.com/fsharp/fslang-suggestions/issues/1136), [PR #18098](https://github.com/dotnet/fsharp/pull/18098)) +* Allow `_` in `use!` bindings values (lift FS1228 restriction) ([PR #18487](https://github.com/dotnet/fsharp/pull/18487)) * Warn when `unit` is passed to an `obj`-typed argument ([PR #18330](https://github.com/dotnet/fsharp/pull/18330)) ### Fixed diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index a7bcc1f9f87..954a41c9a8a 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -4,6 +4,7 @@ /// with generalization at appropriate points. module internal FSharp.Compiler.CheckComputationExpressions +open FSharp.Compiler.TcGlobals open Internal.Utilities.Library open FSharp.Compiler.AccessibilityLogic open FSharp.Compiler.AttributeChecking @@ -1809,17 +1810,29 @@ let rec TryTranslateComputationExpression translatedCtxt ) - // 'use! pat = e1 in e2' --> build.Bind(e1, (function _argN -> match _argN with pat -> build.Using(x, (fun _argN -> match _argN with pat -> e2)))) + // 'use! pat = e1 in e2' --> build.Bind(e1, (function _argN -> match _argN with pat -> build.Using(x, (fun _argN -> match _argN with pat -> e2)))) + // or + // 'use! _ = e1 in e2' --> build.Bind(e1, (fun _argN -> e2)) | ExprAsUseBang(spBind, isFromSource, pat, rhsExpr, andBangs, innerComp, mBind) -> if ceenv.isQuery then error (Error(FSComp.SR.tcBindMayNotBeUsedInQueries (), mBind)) - match pat, andBangs with - | (SynPat.Named(ident = SynIdent(id, _); isThisVal = false) | SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ]))), [] -> + match andBangs with + | [] -> // Valid pattern case - handle with Using + Bind requireBuilderMethod "Using" mBind cenv ceenv.env ceenv.ad ceenv.builderTy mBind requireBuilderMethod "Bind" mBind cenv ceenv.env ceenv.ad ceenv.builderTy mBind + let ident, pat = + match pat with + | SynPat.Named(ident = SynIdent(id, _); isThisVal = false) -> id, pat + | SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ])) -> id, pat + | SynPat.Wild(m) when ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.UseBangBindingValueDiscard -> + // Special handling for wildcard (_) patterns + let tmpIdent = mkSynId m "_" + tmpIdent, SynPat.Named(SynIdent(tmpIdent, None), false, None, m) + | _ -> error (Error(FSComp.SR.tcInvalidUseBangBinding (), pat.Range)) + let bindExpr = let consumeExpr = SynExpr.MatchLambda( @@ -1840,14 +1853,14 @@ let rec TryTranslateComputationExpression ) let consumeExpr = - mkSynCall "Using" mBind [ SynExpr.Ident id; consumeExpr ] ceenv.builderValName + mkSynCall "Using" mBind [ SynExpr.Ident ident; consumeExpr ] ceenv.builderValName let consumeExpr = SynExpr.MatchLambda( false, mBind, [ - SynMatchClause(pat, None, consumeExpr, id.idRange, DebugPointAtTarget.No, SynMatchClauseTrivia.Zero) + SynMatchClause(pat, None, consumeExpr, ident.idRange, DebugPointAtTarget.No, SynMatchClauseTrivia.Zero) ], DebugPointAtBinding.NoneAtInvisible, mBind @@ -1860,8 +1873,7 @@ let rec TryTranslateComputationExpression |> addBindDebugPoint spBind Some(translatedCtxt bindExpr) - | _pat, [] -> error (Error(FSComp.SR.tcInvalidUseBangBinding (), mBind)) - | _pat, _ands -> + | _ -> // Has andBangs let m = match andBangs with diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 9bfa12ce963..d1dad954b3b 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1798,3 +1798,4 @@ featureDontWarnOnUppercaseIdentifiersInBindingPatterns,"Don't warn on uppercase featureDeprecatePlacesWhereSeqCanBeOmitted,"Deprecate places where 'seq' can be omitted" featureSupportValueOptionsAsOptionalParameters,"Support ValueOption as valid type for optional member parameters" featureSupportWarnWhenUnitPassedToObjArg,"Warn when unit is passed to a member accepting `obj` argument, e.g. `Method(o:obj)` will warn if called via `Method()`." +featureUseBangBindingValueDiscard,"Use bang binding with value discard" diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 7a9a14b8602..874b709d658 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -99,6 +99,7 @@ type LanguageFeature = | DeprecatePlacesWhereSeqCanBeOmitted | SupportValueOptionsAsOptionalParameters | WarnWhenUnitPassedToObjArg + | UseBangBindingValueDiscard /// LanguageVersion management type LanguageVersion(versionText) = @@ -229,6 +230,7 @@ type LanguageVersion(versionText) = LanguageFeature.DeprecatePlacesWhereSeqCanBeOmitted, previewVersion LanguageFeature.SupportValueOptionsAsOptionalParameters, previewVersion LanguageFeature.WarnWhenUnitPassedToObjArg, previewVersion + LanguageFeature.UseBangBindingValueDiscard, previewVersion ] static let defaultLanguageVersion = LanguageVersion("default") @@ -391,6 +393,7 @@ type LanguageVersion(versionText) = | LanguageFeature.DeprecatePlacesWhereSeqCanBeOmitted -> FSComp.SR.featureDeprecatePlacesWhereSeqCanBeOmitted () | LanguageFeature.SupportValueOptionsAsOptionalParameters -> FSComp.SR.featureSupportValueOptionsAsOptionalParameters () | LanguageFeature.WarnWhenUnitPassedToObjArg -> FSComp.SR.featureSupportWarnWhenUnitPassedToObjArg () + | LanguageFeature.UseBangBindingValueDiscard -> FSComp.SR.featureUseBangBindingValueDiscard () /// Get a version string associated with the given feature. static member GetFeatureVersionString feature = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index 410a8b193c9..f50357f3fe0 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -90,6 +90,7 @@ type LanguageFeature = | DeprecatePlacesWhereSeqCanBeOmitted | SupportValueOptionsAsOptionalParameters | WarnWhenUnitPassedToObjArg + | UseBangBindingValueDiscard /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy index 7cac0ad0dc0..e6e3a657592 100644 --- a/src/Compiler/pars.fsy +++ b/src/Compiler/pars.fsy @@ -3755,13 +3755,23 @@ atomicPattern: { SynPat.ArrayOrList(true, $2, lhs parseState) } | UNDERSCORE - { SynPat.Wild(lhs parseState) } + { (* Underscore pattern ('_') is represented as SynPat.Wild + This wild pattern is used in all binding forms: + - let _ = ... + - use _ = ... + - let! _ = ... + - use! _ = ... + This ensures consistent representation of wildcard bindings in the AST *) + SynPat.Wild(lhs parseState) } | QMARK ident { SynPat.OptionalVal($2, lhs parseState) } | atomicPatternLongIdent %prec prec_atompat_pathop - { let vis, lidwd = $1 + { (* This rule handles identifiers in patterns like 'use! __' *) + (* For simple identifiers (like '__'), it creates a SynPat.Named AST node *) + (* For complex paths (A.B.C) or uppercase ids, it calls mkSynPatMaybeVar *) + let vis, lidwd = $1 if not (isNilOrSingleton lidwd.LongIdent) || String.isLeadingIdentifierCharacterUpperCase (List.head lidwd.LongIdent).idText then mkSynPatMaybeVar lidwd vis (lhs parseState) else @@ -4431,7 +4441,13 @@ declExpr: SynExpr.YieldOrReturnFrom(($1, not $1), arbExpr ("yield!", mYieldAll), mYieldAll, trivia) } | BINDER headBindingPattern EQUALS typedSequentialExprBlock IN opt_OBLOCKSEP moreBinders typedSequentialExprBlock %prec expr_let - { let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 5) + { (* This rule handles the 'use!' and 'let!' binding expressions in computation expressions *) + (* The BINDER token represents keywords like 'use!' or 'let!' *) + (* headBindingPattern represents patterns in the binding: + - Underscore ('_') patterns are preserved as SynPat.Wild + - Named patterns ('__') are represented as SynPat.Named + - Identifiers (like 'value') are represented as SynPat.LongIdent *) + let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 5) let mEquals = rhs parseState 3 let m = unionRanges (rhs parseState 1) $8.Range let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals } diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 7d64601c303..491e8a205c2 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -657,6 +657,11 @@ Interoperabilita mezi neřízeným obecným omezením jazyka C# a F# (emitovat další modreq) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 67f4270377e..ada82f801ce 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -657,6 +657,11 @@ Interop zwischen nicht verwalteter generischer Einschränkung in C# und F# (zusätzlicher ModReq ausgeben) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 9478e3d2c55..d968092fd27 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -657,6 +657,11 @@ Interoperabilidad entre la restricción genérica no administrada de C# y F# (emitir modreq adicional) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 57fade5d250..ad43afb8a8a 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -657,6 +657,11 @@ Interopérabilité entre les contraintes génériques non gérées de C# et F# (émettre un modreq supplémentaire) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 14d670e8455..8abe6bcd59a 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -657,6 +657,11 @@ Interoperabilità tra il vincolo generico non gestito di C# e di F# (crea un modreq aggiuntivo) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 78acb0fd944..693b5af58bb 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -657,6 +657,11 @@ C# と F# のアンマネージド ジェネリック制約の間の相互運用 (追加の modreq を出力) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 604ac431e4a..cd9028b19a7 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -657,6 +657,11 @@ C#과 F#의 관리되지 않는 제네릭 제약 조건 간의 Interop(추가 modreq 내보내기) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index fdee3d82f7d..9b0d13da5da 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -657,6 +657,11 @@ Międzyoperacyjnie między niezarządzanym ograniczeniem ogólnym języka C# i F# (emituj dodatkowe modreq) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 2f86c57d960..10920378dcc 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -657,6 +657,11 @@ Interoperabilidade entre a restrição genérica não gerenciada de C# e F# (emitir modreq adicional) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index fefd5255a0b..f15fa632ad4 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -657,6 +657,11 @@ Взаимодействие между универсальным ограничением "unmanaged" C# и F#(создание дополнительного modreq) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 5325f0ab09f..9e26ccb98db 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -657,6 +657,11 @@ C# ile F#' arasında yönetilmeyen genel kısıtlama (ek modreq yayın) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 2e8b957d810..5b707c0ba2d 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -657,6 +657,11 @@ C# 和 F# 的非托管泛型约束之间的互操作(发出额外的 modreq) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index f0924b3d30f..86b0c173425 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -657,6 +657,11 @@ C# 與 F# 的非受控泛型條件約束之間的 Interop (發出額外的 modreq) + + Use bang binding with value discard + Use bang binding with value discard + + Use type conversion cache during compilation Use type conversion cache during compilation diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 60b867c7815..fcea528d953 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -232,6 +232,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs new file mode 100644 index 00000000000..dbff980fef8 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs @@ -0,0 +1,336 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Language + +open Xunit +open FSharp.Test.Compiler + +module UseBangBindingsVersion9Tests = + [] + let ``Error when using discard pattern in use! binding`` () = + FSharp """ +module Program +open System +type Counter(value: int) = + member _.Value = value + interface IDisposable with + member _.Dispose() = () + +let getCounterAsync initial = async { + return new Counter(initial) +} + +let exampleAsync() = async { + use! counter = getCounterAsync 5 + use! __ = getCounterAsync 4 + use! _ = getCounterAsync 3 + return () +} + +[] +let main _ = + exampleAsync() |> Async.RunSynchronously + 0 + """ + |> withLangVersion90 + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 1228, Line 16, Col 10, Line 16, Col 11, "'use!' bindings must be of the form 'use! = '") + ] + + [] + let ``Named patterns are allowed in use! binding`` () = + FSharp """ +module Program +let doSomething () = + async { + use _ = { new System.IDisposable with member _.Dispose() = printfn "disposed 1" } + use! __ = Async.OnCancel (fun () -> printfn "disposed 2") + use! res2 = Async.OnCancel (fun () -> printfn "disposed 3") + do! Async.Sleep(100) + return () + } + +[] +let main _ = + let cts = new System.Threading.CancellationTokenSource() + Async.Start(doSomething(), cts.Token) + System.Threading.Thread.Sleep(100) + cts.Cancel() + 0 + """ + |> withLangVersion90 + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "disposed 1" + "disposed 3" + "disposed 2" + ] + + [] + let ``Both named and discard patterns in custom computation expression use! binding`` () = + FSharp """ +module Program +open System +type Counter(value: int) = + member _.Value = value + + interface IDisposable with + member _.Dispose() = + printfn $"Counter with value %d{value} disposed" + +type CounterBuilder() = + member _.Using(resource: #IDisposable, f) = + async { + use res = resource + return! f res + } + + member _.Bind(counter: Counter, f) = + async { + let c = counter + return! f c + } + + member _.Return(x) = async.Return x + + member _.ReturnFrom(x) = x + + member _.Bind(task, f) = async.Bind(task, f) + +[] +let main _ = + let counterBuilder = CounterBuilder() + + let example() = + counterBuilder { + use! res = new Counter(5) + use! __ = new Counter(4) + do! Async.Sleep 1000 + return () + } + + example() + |> Async.RunSynchronously + 0 + """ + |> withLangVersion90 + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "Counter with value 4 disposed" + "Counter with value 5 disposed" + ] + +module UseBangBindingsPreviewTests = + [] + let ``Discard pattern allowed in async use! binding`` () = + FSharp """ +module Program + +open System + +type Counter(value: int) = + member _.Value = value + + interface IDisposable with + member _.Dispose() = + printfn $"Counter with value %d{value} disposed" + +let getCounterAsync initial = async { + do! Async.Sleep 100 // Simulate async work + printfn $"Created counter with value %d{initial}" + return new Counter(initial) +} + +let exampleAsync() = async { + printfn "Starting async workflow" + use! counter = getCounterAsync 5 + use! __ = getCounterAsync 4 + use! _ = getCounterAsync 3 + printfn "Done using the resource" + return () +} + +[] +let main _ = + exampleAsync() |> Async.RunSynchronously + 0 + """ + |> withLangVersionPreview + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "Starting async workflow" + "Created counter with value 5" + "Created counter with value 4" + "Created counter with value 3" + "Done using the resource" + "Counter with value 3 disposed" + "Counter with value 4 disposed" + "Counter with value 5 disposed" + ] + + [] + let ``Discard pattern allowed in async use! binding 2`` () = + FSharp """ +module Program +let doSomething () = + async { + use _ = { new System.IDisposable with member _.Dispose() = printfn "disposed 1" } + use! __ = Async.OnCancel (fun () -> printfn "disposed 2") + use! res2 = Async.OnCancel (fun () -> printfn "disposed 3") + use! _ = Async.OnCancel (fun () -> printfn "disposed 4") + do! Async.Sleep(100) + return () + } + +[] +let main _ = + let cts = new System.Threading.CancellationTokenSource() + Async.Start(doSomething(), cts.Token) + System.Threading.Thread.Sleep(100) + cts.Cancel() + 0 + """ + |> withLangVersionPreview + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "disposed 1" + "disposed 4" + "disposed 3" + "disposed 2" + ] + + [] + let ``Both named and discard patterns in custom computation expression use! binding`` () = + FSharp """ +module Program +open System +type Counter(value: int) = + member _.Value = value + + interface IDisposable with + member _.Dispose() = + printfn $"Counter with value %d{value} disposed" + +type CounterBuilder() = + member _.Using(resource: #IDisposable, f) = + async { + use res = resource + return! f res + } + + member _.Bind(counter: Counter, f) = + async { + let c = counter + return! f c + } + + member _.Return(x) = async.Return x + + member _.ReturnFrom(x) = x + + member _.Bind(task, f) = async.Bind(task, f) + +[] +let main _ = + let counterBuilder = CounterBuilder() + + let example() = + counterBuilder { + use! res = new Counter(5) + use! __ = new Counter(4) + use! _ = new Counter(3) + do! Async.Sleep 1000 + return () + } + + example() + |> Async.RunSynchronously + 0 + """ + |> withLangVersionPreview + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "Counter with value 3 disposed" + "Counter with value 4 disposed" + "Counter with value 5 disposed" + ] + + [] + let ``Discard patterns are allowed in use and use! binding`` () = + FSharp """ +module Program +open System +type Counter(value: int) = + member _.Value = value + + interface IDisposable with + member _.Dispose() = + printfn $"Counter with value %d{value} disposed" + +type CounterBuilder() = + member _.Using(resource: #IDisposable, f) = + async { + use res = resource + return! f res + } + + member _.Bind(counter: Counter, f) = + async { + let c = counter + return! f c + } + + member _.Return(x) = async.Return x + + member _.ReturnFrom(x) = x + + member _.Bind(task, f) = async.Bind(task, f) + +[] +let main _ = + let counterBuilder = CounterBuilder() + + let example() = + counterBuilder { + use _ = { new System.IDisposable with member _.Dispose() = printfn "disposed 1" } + use! _ = new Counter(2) + do! Async.Sleep 1000 + return () + } + + example() + |> Async.RunSynchronously + 0 + """ + |> withLangVersionPreview + |> asExe + |> compile + |> shouldSucceed + |> run + |> shouldSucceed + |> withOutputContainsAllInOrder [ + "Counter with value 2 disposed" + "disposed 1" + ] \ No newline at end of file From f7c96207406200ce6d7f13f724837d205d5fa8ea Mon Sep 17 00:00:00 2001 From: Edgar Gonzalez Date: Sun, 20 Apr 2025 21:13:11 +0100 Subject: [PATCH 03/10] update tests --- .../Language/UseBangBindingsTests.fs | 63 ++++++++----------- 1 file changed, 26 insertions(+), 37 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs index dbff980fef8..4559d2cc745 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs @@ -2,6 +2,7 @@ namespace Language +open FSharp.Test open Xunit open FSharp.Test.Compiler @@ -39,7 +40,7 @@ let main _ = (Error 1228, Line 16, Col 10, Line 16, Col 11, "'use!' bindings must be of the form 'use! = '") ] - [] + [] let ``Named patterns are allowed in use! binding`` () = FSharp """ module Program @@ -66,11 +67,9 @@ let main _ = |> shouldSucceed |> run |> shouldSucceed - |> withOutputContainsAllInOrder [ - "disposed 1" - "disposed 3" - "disposed 2" - ] + |> withStdOutContains "disposed 1" + |> withStdOutContains "disposed 2" + |> withStdOutContains "disposed 3" [] let ``Both named and discard patterns in custom computation expression use! binding`` () = @@ -125,10 +124,8 @@ let main _ = |> shouldSucceed |> run |> shouldSucceed - |> withOutputContainsAllInOrder [ - "Counter with value 4 disposed" - "Counter with value 5 disposed" - ] + |> withStdOutContains "Counter with value 4 disposed" + |> withStdOutContains "Counter with value 5 disposed" module UseBangBindingsPreviewTests = [] @@ -171,18 +168,16 @@ let main _ = |> shouldSucceed |> run |> shouldSucceed - |> withOutputContainsAllInOrder [ - "Starting async workflow" - "Created counter with value 5" - "Created counter with value 4" - "Created counter with value 3" - "Done using the resource" - "Counter with value 3 disposed" - "Counter with value 4 disposed" - "Counter with value 5 disposed" - ] - - [] + |> withStdOutContains "Starting async workflow" + |> withStdOutContains "Created counter with value 5" + |> withStdOutContains "Created counter with value 4" + |> withStdOutContains "Created counter with value 3" + |> withStdOutContains "Done using the resource" + |> withStdOutContains "Counter with value 3 disposed" + |> withStdOutContains "Counter with value 4 disposed" + |> withStdOutContains "Counter with value 5 disposed" + + [] let ``Discard pattern allowed in async use! binding 2`` () = FSharp """ module Program @@ -210,12 +205,10 @@ let main _ = |> shouldSucceed |> run |> shouldSucceed - |> withOutputContainsAllInOrder [ - "disposed 1" - "disposed 4" - "disposed 3" - "disposed 2" - ] + |> withStdOutContains "disposed 1" + |> withStdOutContains "disposed 2" + |> withStdOutContains "disposed 3" + |> withStdOutContains "disposed 4" [] let ``Both named and discard patterns in custom computation expression use! binding`` () = @@ -271,11 +264,9 @@ let main _ = |> shouldSucceed |> run |> shouldSucceed - |> withOutputContainsAllInOrder [ - "Counter with value 3 disposed" - "Counter with value 4 disposed" - "Counter with value 5 disposed" - ] + |> withStdOutContains "Counter with value 3 disposed" + |> withStdOutContains "Counter with value 4 disposed" + |> withStdOutContains "Counter with value 5 disposed" [] let ``Discard patterns are allowed in use and use! binding`` () = @@ -330,7 +321,5 @@ let main _ = |> shouldSucceed |> run |> shouldSucceed - |> withOutputContainsAllInOrder [ - "Counter with value 2 disposed" - "disposed 1" - ] \ No newline at end of file + |> withStdOutContains "disposed 1" + |> withStdOutContains "Counter with value 2 disposed" \ No newline at end of file From a3036ba0265735d2de95e131f90f0c8ca20466eb Mon Sep 17 00:00:00 2001 From: Edgar Gonzalez Date: Mon, 21 Apr 2025 17:20:37 +0100 Subject: [PATCH 04/10] UnwrapUseBang --- .../CheckComputationExpressions.fs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index 954a41c9a8a..315626e3487 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -850,6 +850,17 @@ let (|OptionalSequential|) e = | SynExpr.Sequential(debugPoint = _sp; isTrueSeq = true; expr1 = dataComp1; expr2 = dataComp2) -> (dataComp1, Some dataComp2) | _ -> (e, None) +[] +let rec (|UnwrapUseBang|_|) supportsUseBangBindingValueDiscard pat = + match pat with + | SynPat.Named(ident = SynIdent(id, _); isThisVal = false) -> ValueSome(id, pat) + | SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ])) -> ValueSome(id, pat) + | SynPat.Wild(m) when supportsUseBangBindingValueDiscard -> + let tmpIdent = mkSynId m "_" + ValueSome(tmpIdent, SynPat.Named(SynIdent(tmpIdent, None), false, None, m)) + | SynPat.Paren(pat = UnwrapUseBang supportsUseBangBindingValueDiscard (id, pat)) -> ValueSome(id, pat) + | _ -> ValueNone + [] let (|ExprAsUseBang|_|) expr = match expr with @@ -1823,14 +1834,12 @@ let rec TryTranslateComputationExpression requireBuilderMethod "Using" mBind cenv ceenv.env ceenv.ad ceenv.builderTy mBind requireBuilderMethod "Bind" mBind cenv ceenv.env ceenv.ad ceenv.builderTy mBind + let supportsUseBangBindingValueDiscard = + ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.UseBangBindingValueDiscard + let ident, pat = match pat with - | SynPat.Named(ident = SynIdent(id, _); isThisVal = false) -> id, pat - | SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ])) -> id, pat - | SynPat.Wild(m) when ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.UseBangBindingValueDiscard -> - // Special handling for wildcard (_) patterns - let tmpIdent = mkSynId m "_" - tmpIdent, SynPat.Named(SynIdent(tmpIdent, None), false, None, m) + | UnwrapUseBang supportsUseBangBindingValueDiscard (ident, pat) -> ident, pat | _ -> error (Error(FSComp.SR.tcInvalidUseBangBinding (), pat.Range)) let bindExpr = From 27e201257efd8b333848440d8d0c509d170d0018 Mon Sep 17 00:00:00 2001 From: Edgar Gonzalez Date: Mon, 21 Apr 2025 17:20:50 +0100 Subject: [PATCH 05/10] Improve tests --- .../UseBindings/UseBang01.fs | 55 +++ .../UseBindings/UseBangBindings.fs | 28 ++ .../UseBindings/UseBindingDiscard02.fs | 26 ++ .../UseBindings/UseBindings.fs | 32 +- .../FSharp.Compiler.ComponentTests.fsproj | 2 +- .../Language/UseBangBindingsTests.fs | 325 ------------------ 6 files changed, 113 insertions(+), 355 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang01.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBangBindings.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindingDiscard02.fs delete mode 100644 tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang01.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang01.fs new file mode 100644 index 00000000000..20700f5cd52 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang01.fs @@ -0,0 +1,55 @@ +open System + +type Disposable() = + [] static val mutable private disposedTimes: int + [] static val mutable private constructedTimes: int + + do Disposable.constructedTimes <- Disposable.constructedTimes + 1 + + static member DisposeCallCount() = Disposable.disposedTimes + static member ConstructorCallCount() = Disposable.constructedTimes + + interface IDisposable with + member _.Dispose() = + Disposable.disposedTimes <- Disposable.disposedTimes + 1 + +type DisposableBuilder() = + member _.Using(resource: #IDisposable, f) = + async { + use res = resource + return! f res + } + + member _.Bind(disposable: Disposable, f) = + async { + let c = disposable + return! f c + } + + member _.Return(x) = async.Return x + + member _.ReturnFrom(x) = x + + member _.Bind(task, f) = async.Bind(task, f) + +let counterDisposable = DisposableBuilder() + +let example() = + counterDisposable { + use! res = new Disposable() + use! __ = new Disposable() + use! (res1) = new Disposable() + use! (___) = new Disposable() + use! _ = new Disposable() + return () + } + +example() +|> Async.RunSynchronously + +let disposeCalls = Disposable.DisposeCallCount() +if disposeCalls <> 5 then + failwithf $"unexpected dispose call count: %i{disposeCalls}" +let ctorCalls = Disposable.ConstructorCallCount() +if ctorCalls <> 5 then + failwithf $"unexpected constructor call count: %i{ctorCalls}" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBangBindings.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBangBindings.fs new file mode 100644 index 00000000000..c9f1b532f00 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBangBindings.fs @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Language + +open FSharp.Test +open Xunit +open FSharp.Test.Compiler + +module UseBangBindingsVersion9 = + [] + let ``UseBangBindings - UseBang01_fs - Current LangVersion`` compilation = + compilation + |> asFsx + |> withLangVersion90 + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 1228, Line 43, Col 14, Line 43, Col 15, "'use!' bindings must be of the form 'use! = '") + ] + +module UseBangBindingsPreview = + [] + let ``UseBangBindings - UseBang01_fs - Preview LangVersion`` compilation = + compilation + |> asExe + |> withLangVersionPreview + |> compileAndRun + |> shouldSucceed diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindingDiscard02.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindingDiscard02.fs new file mode 100644 index 00000000000..9a31ba2763b --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindingDiscard02.fs @@ -0,0 +1,26 @@ +open System + +type private Disposable() = + [] static val mutable private disposedTimes: int + [] static val mutable private constructedTimes: int + + do Disposable.constructedTimes <- Disposable.constructedTimes + 1 + + static member DisposeCallCount() = Disposable.disposedTimes + static member ConstructorCallCount() = Disposable.constructedTimes + + interface System.IDisposable with + member _.Dispose() = + Disposable.disposedTimes <- Disposable.disposedTimes + 1 + +let _scope = + use _ = new Disposable() + () + +let disposeCalls = Disposable.DisposeCallCount() +if disposeCalls <> 1 then + failwith "was not disposed or disposed too many times" + +let ctorCalls = Disposable.ConstructorCallCount() +if ctorCalls <> 1 then + failwithf "unexpected constructor call count: %i" ctorCalls diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindings.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindings.fs index 8196a9fe286..74137286a8a 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindings.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBindings.fs @@ -27,35 +27,9 @@ module UseBindings = |> withErrorCode 3350 |> withDiagnosticMessageMatches "Feature 'discard pattern in use binding' is not available.*" - [] - let ``Dispose called for discarded value of use binding`` () = - Fsx """ -type private Disposable() = - [] static val mutable private disposedTimes: int - [] static val mutable private constructedTimes: int - - do Disposable.constructedTimes <- Disposable.constructedTimes + 1 - - static member DisposeCallCount() = Disposable.disposedTimes - static member ConstructorCallCount() = Disposable.constructedTimes - - interface System.IDisposable with - member _.Dispose() = - Disposable.disposedTimes <- Disposable.disposedTimes + 1 - -let _scope = - use _ = new Disposable() - () - -let disposeCalls = Disposable.DisposeCallCount() -if disposeCalls <> 1 then - failwith "was not disposed or disposed too many times" - -let ctorCalls = Disposable.ConstructorCallCount() -if ctorCalls <> 1 then - failwithf "unexpected constructor call count: %i" ctorCalls - - """ + [] + let ``Dispose called for discarded value of use binding`` compilation = + compilation |> asExe |> withLangVersion60 |> compileAndRun diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index fcea528d953..f8a54229d1e 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -76,6 +76,7 @@ + @@ -232,7 +233,6 @@ - diff --git a/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs deleted file mode 100644 index 4559d2cc745..00000000000 --- a/tests/FSharp.Compiler.ComponentTests/Language/UseBangBindingsTests.fs +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. - -namespace Language - -open FSharp.Test -open Xunit -open FSharp.Test.Compiler - -module UseBangBindingsVersion9Tests = - [] - let ``Error when using discard pattern in use! binding`` () = - FSharp """ -module Program -open System -type Counter(value: int) = - member _.Value = value - interface IDisposable with - member _.Dispose() = () - -let getCounterAsync initial = async { - return new Counter(initial) -} - -let exampleAsync() = async { - use! counter = getCounterAsync 5 - use! __ = getCounterAsync 4 - use! _ = getCounterAsync 3 - return () -} - -[] -let main _ = - exampleAsync() |> Async.RunSynchronously - 0 - """ - |> withLangVersion90 - |> typecheck - |> shouldFail - |> withDiagnostics [ - (Error 1228, Line 16, Col 10, Line 16, Col 11, "'use!' bindings must be of the form 'use! = '") - ] - - [] - let ``Named patterns are allowed in use! binding`` () = - FSharp """ -module Program -let doSomething () = - async { - use _ = { new System.IDisposable with member _.Dispose() = printfn "disposed 1" } - use! __ = Async.OnCancel (fun () -> printfn "disposed 2") - use! res2 = Async.OnCancel (fun () -> printfn "disposed 3") - do! Async.Sleep(100) - return () - } - -[] -let main _ = - let cts = new System.Threading.CancellationTokenSource() - Async.Start(doSomething(), cts.Token) - System.Threading.Thread.Sleep(100) - cts.Cancel() - 0 - """ - |> withLangVersion90 - |> asExe - |> compile - |> shouldSucceed - |> run - |> shouldSucceed - |> withStdOutContains "disposed 1" - |> withStdOutContains "disposed 2" - |> withStdOutContains "disposed 3" - - [] - let ``Both named and discard patterns in custom computation expression use! binding`` () = - FSharp """ -module Program -open System -type Counter(value: int) = - member _.Value = value - - interface IDisposable with - member _.Dispose() = - printfn $"Counter with value %d{value} disposed" - -type CounterBuilder() = - member _.Using(resource: #IDisposable, f) = - async { - use res = resource - return! f res - } - - member _.Bind(counter: Counter, f) = - async { - let c = counter - return! f c - } - - member _.Return(x) = async.Return x - - member _.ReturnFrom(x) = x - - member _.Bind(task, f) = async.Bind(task, f) - -[] -let main _ = - let counterBuilder = CounterBuilder() - - let example() = - counterBuilder { - use! res = new Counter(5) - use! __ = new Counter(4) - do! Async.Sleep 1000 - return () - } - - example() - |> Async.RunSynchronously - 0 - """ - |> withLangVersion90 - |> asExe - |> compile - |> shouldSucceed - |> run - |> shouldSucceed - |> withStdOutContains "Counter with value 4 disposed" - |> withStdOutContains "Counter with value 5 disposed" - -module UseBangBindingsPreviewTests = - [] - let ``Discard pattern allowed in async use! binding`` () = - FSharp """ -module Program - -open System - -type Counter(value: int) = - member _.Value = value - - interface IDisposable with - member _.Dispose() = - printfn $"Counter with value %d{value} disposed" - -let getCounterAsync initial = async { - do! Async.Sleep 100 // Simulate async work - printfn $"Created counter with value %d{initial}" - return new Counter(initial) -} - -let exampleAsync() = async { - printfn "Starting async workflow" - use! counter = getCounterAsync 5 - use! __ = getCounterAsync 4 - use! _ = getCounterAsync 3 - printfn "Done using the resource" - return () -} - -[] -let main _ = - exampleAsync() |> Async.RunSynchronously - 0 - """ - |> withLangVersionPreview - |> asExe - |> compile - |> shouldSucceed - |> run - |> shouldSucceed - |> withStdOutContains "Starting async workflow" - |> withStdOutContains "Created counter with value 5" - |> withStdOutContains "Created counter with value 4" - |> withStdOutContains "Created counter with value 3" - |> withStdOutContains "Done using the resource" - |> withStdOutContains "Counter with value 3 disposed" - |> withStdOutContains "Counter with value 4 disposed" - |> withStdOutContains "Counter with value 5 disposed" - - [] - let ``Discard pattern allowed in async use! binding 2`` () = - FSharp """ -module Program -let doSomething () = - async { - use _ = { new System.IDisposable with member _.Dispose() = printfn "disposed 1" } - use! __ = Async.OnCancel (fun () -> printfn "disposed 2") - use! res2 = Async.OnCancel (fun () -> printfn "disposed 3") - use! _ = Async.OnCancel (fun () -> printfn "disposed 4") - do! Async.Sleep(100) - return () - } - -[] -let main _ = - let cts = new System.Threading.CancellationTokenSource() - Async.Start(doSomething(), cts.Token) - System.Threading.Thread.Sleep(100) - cts.Cancel() - 0 - """ - |> withLangVersionPreview - |> asExe - |> compile - |> shouldSucceed - |> run - |> shouldSucceed - |> withStdOutContains "disposed 1" - |> withStdOutContains "disposed 2" - |> withStdOutContains "disposed 3" - |> withStdOutContains "disposed 4" - - [] - let ``Both named and discard patterns in custom computation expression use! binding`` () = - FSharp """ -module Program -open System -type Counter(value: int) = - member _.Value = value - - interface IDisposable with - member _.Dispose() = - printfn $"Counter with value %d{value} disposed" - -type CounterBuilder() = - member _.Using(resource: #IDisposable, f) = - async { - use res = resource - return! f res - } - - member _.Bind(counter: Counter, f) = - async { - let c = counter - return! f c - } - - member _.Return(x) = async.Return x - - member _.ReturnFrom(x) = x - - member _.Bind(task, f) = async.Bind(task, f) - -[] -let main _ = - let counterBuilder = CounterBuilder() - - let example() = - counterBuilder { - use! res = new Counter(5) - use! __ = new Counter(4) - use! _ = new Counter(3) - do! Async.Sleep 1000 - return () - } - - example() - |> Async.RunSynchronously - 0 - """ - |> withLangVersionPreview - |> asExe - |> compile - |> shouldSucceed - |> run - |> shouldSucceed - |> withStdOutContains "Counter with value 3 disposed" - |> withStdOutContains "Counter with value 4 disposed" - |> withStdOutContains "Counter with value 5 disposed" - - [] - let ``Discard patterns are allowed in use and use! binding`` () = - FSharp """ -module Program -open System -type Counter(value: int) = - member _.Value = value - - interface IDisposable with - member _.Dispose() = - printfn $"Counter with value %d{value} disposed" - -type CounterBuilder() = - member _.Using(resource: #IDisposable, f) = - async { - use res = resource - return! f res - } - - member _.Bind(counter: Counter, f) = - async { - let c = counter - return! f c - } - - member _.Return(x) = async.Return x - - member _.ReturnFrom(x) = x - - member _.Bind(task, f) = async.Bind(task, f) - -[] -let main _ = - let counterBuilder = CounterBuilder() - - let example() = - counterBuilder { - use _ = { new System.IDisposable with member _.Dispose() = printfn "disposed 1" } - use! _ = new Counter(2) - do! Async.Sleep 1000 - return () - } - - example() - |> Async.RunSynchronously - 0 - """ - |> withLangVersionPreview - |> asExe - |> compile - |> shouldSucceed - |> run - |> shouldSucceed - |> withStdOutContains "disposed 1" - |> withStdOutContains "Counter with value 2 disposed" \ No newline at end of file From 2ccad7fb211da079f983d1c699a6345763a4dfdb Mon Sep 17 00:00:00 2001 From: edgargonzalez Date: Tue, 22 Apr 2025 16:57:23 +0100 Subject: [PATCH 06/10] Add comments --- .../Checking/Expressions/CheckComputationExpressions.fs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index 315626e3487..4e9a2dbfbb7 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -850,12 +850,18 @@ let (|OptionalSequential|) e = | SynExpr.Sequential(debugPoint = _sp; isTrueSeq = true; expr1 = dataComp1; expr2 = dataComp2) -> (dataComp1, Some dataComp2) | _ -> (e, None) +// use! x = ... +// use! (x) = ... +// use! (__) = ... +// use! _ = ... +// use! (_) = ... [] let rec (|UnwrapUseBang|_|) supportsUseBangBindingValueDiscard pat = match pat with | SynPat.Named(ident = SynIdent(id, _); isThisVal = false) -> ValueSome(id, pat) | SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ])) -> ValueSome(id, pat) | SynPat.Wild(m) when supportsUseBangBindingValueDiscard -> + // To properly call the Using(disposable) CE member, we need to convert the wildcard to a SynPat.Named let tmpIdent = mkSynId m "_" ValueSome(tmpIdent, SynPat.Named(SynIdent(tmpIdent, None), false, None, m)) | SynPat.Paren(pat = UnwrapUseBang supportsUseBangBindingValueDiscard (id, pat)) -> ValueSome(id, pat) From c12e2476d5435c076952f1563b91cb9e30172d43 Mon Sep 17 00:00:00 2001 From: edgargonzalez Date: Wed, 23 Apr 2025 18:42:33 +0100 Subject: [PATCH 07/10] update tests --- .../BasicGrammarElements/UseBindings/UseBang01.fs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang01.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang01.fs index 20700f5cd52..10d4c2eac5f 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang01.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang01.fs @@ -41,6 +41,9 @@ let example() = use! (res1) = new Disposable() use! (___) = new Disposable() use! _ = new Disposable() + use! (_) = new Disposable() + use! _res2 = new Disposable() + use! (_res3) = new Disposable() return () } @@ -48,8 +51,8 @@ example() |> Async.RunSynchronously let disposeCalls = Disposable.DisposeCallCount() -if disposeCalls <> 5 then +if disposeCalls <> 8 then failwithf $"unexpected dispose call count: %i{disposeCalls}" let ctorCalls = Disposable.ConstructorCallCount() -if ctorCalls <> 5 then +if ctorCalls <> 8 then failwithf $"unexpected constructor call count: %i{ctorCalls}" \ No newline at end of file From 303a4c04d6f6e92054e3ff278813d1dd8bbe2f87 Mon Sep 17 00:00:00 2001 From: edgargonzalez Date: Thu, 24 Apr 2025 19:58:41 +0100 Subject: [PATCH 08/10] remove unnecessary active pattern --- .../CheckComputationExpressions.fs | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index 4e9a2dbfbb7..91d1ed6228c 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -850,23 +850,6 @@ let (|OptionalSequential|) e = | SynExpr.Sequential(debugPoint = _sp; isTrueSeq = true; expr1 = dataComp1; expr2 = dataComp2) -> (dataComp1, Some dataComp2) | _ -> (e, None) -// use! x = ... -// use! (x) = ... -// use! (__) = ... -// use! _ = ... -// use! (_) = ... -[] -let rec (|UnwrapUseBang|_|) supportsUseBangBindingValueDiscard pat = - match pat with - | SynPat.Named(ident = SynIdent(id, _); isThisVal = false) -> ValueSome(id, pat) - | SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ])) -> ValueSome(id, pat) - | SynPat.Wild(m) when supportsUseBangBindingValueDiscard -> - // To properly call the Using(disposable) CE member, we need to convert the wildcard to a SynPat.Named - let tmpIdent = mkSynId m "_" - ValueSome(tmpIdent, SynPat.Named(SynIdent(tmpIdent, None), false, None, m)) - | SynPat.Paren(pat = UnwrapUseBang supportsUseBangBindingValueDiscard (id, pat)) -> ValueSome(id, pat) - | _ -> ValueNone - [] let (|ExprAsUseBang|_|) expr = match expr with @@ -1843,11 +1826,24 @@ let rec TryTranslateComputationExpression let supportsUseBangBindingValueDiscard = ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.UseBangBindingValueDiscard - let ident, pat = + // use! x = ... + // use! (x) = ... + // use! (__) = ... + // use! _ = ... + // use! (_) = ... + let rec extractIdentifierFromPattern pat = match pat with - | UnwrapUseBang supportsUseBangBindingValueDiscard (ident, pat) -> ident, pat + | SynPat.Named(ident = SynIdent(id, _); isThisVal = false) -> id, pat + | SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ])) -> id, pat + | SynPat.Wild(m) when supportsUseBangBindingValueDiscard -> + // To properly call the Using(disposable) CE member, we need to convert the wildcard to a SynPat.Named + let tmpIdent = mkSynId m "_" + tmpIdent, SynPat.Named(SynIdent(tmpIdent, None), false, None, m) + | SynPat.Paren(pat = pat) -> extractIdentifierFromPattern pat | _ -> error (Error(FSComp.SR.tcInvalidUseBangBinding (), pat.Range)) + let ident, pat = extractIdentifierFromPattern pat + let bindExpr = let consumeExpr = SynExpr.MatchLambda( From d1532fe7ab528d47f3152080ff1d22d6346aaa54 Mon Sep 17 00:00:00 2001 From: edgargonzalez Date: Thu, 24 Apr 2025 20:46:05 +0100 Subject: [PATCH 09/10] more tests --- .../UseBindings/UseBang01.fs | 74 +++++++-------- .../UseBindings/UseBang02.fs | 57 ++++++++++++ .../UseBindings/UseBang03.fs | 71 +++++++++++++++ .../UseBindings/UseBang04.fs | 91 +++++++++++++++++++ .../UseBindings/UseBangBindings.fs | 58 ++++++++++++ 5 files changed, 314 insertions(+), 37 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang02.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang03.fs create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang04.fs diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang01.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang01.fs index 10d4c2eac5f..4b6335c3789 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang01.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang01.fs @@ -1,17 +1,23 @@ open System -type Disposable() = - [] static val mutable private disposedTimes: int - [] static val mutable private constructedTimes: int - - do Disposable.constructedTimes <- Disposable.constructedTimes + 1 - - static member DisposeCallCount() = Disposable.disposedTimes - static member ConstructorCallCount() = Disposable.constructedTimes +open System +type Disposable(id: int) = + static let mutable disposedIds = Set.empty + static let mutable constructedIds = Set.empty + + do constructedIds <- constructedIds.Add(id) + + member _.Id = id + + static member GetDisposed() = disposedIds + static member GetConstructed() = constructedIds + static member Reset() = + disposedIds <- Set.empty + constructedIds <- Set.empty + interface IDisposable with - member _.Dispose() = - Disposable.disposedTimes <- Disposable.disposedTimes + 1 + member this.Dispose() = disposedIds <- disposedIds.Add(this.Id) type DisposableBuilder() = member _.Using(resource: #IDisposable, f) = @@ -20,39 +26,33 @@ type DisposableBuilder() = return! f res } - member _.Bind(disposable: Disposable, f) = - async { - let c = disposable - return! f c - } - + member _.Bind(disposable: Disposable, f) = async.Bind(async.Return(disposable), f) member _.Return(x) = async.Return x - member _.ReturnFrom(x) = x - member _.Bind(task, f) = async.Bind(task, f) let counterDisposable = DisposableBuilder() -let example() = +let testBindingPatterns() = + Disposable.Reset() + counterDisposable { - use! res = new Disposable() - use! __ = new Disposable() - use! (res1) = new Disposable() - use! (___) = new Disposable() - use! _ = new Disposable() - use! (_) = new Disposable() - use! _res2 = new Disposable() - use! (_res3) = new Disposable() + use! res = new Disposable(1) + use! __ = new Disposable(2) + use! (res1) = new Disposable(3) + use! _ = new Disposable(4) + use! (_) = new Disposable(5) return () - } - -example() -|> Async.RunSynchronously + } |> Async.RunSynchronously + + let constructed = Disposable.GetConstructed() + let disposed = Disposable.GetDisposed() + let undisposed = constructed - disposed + + if not undisposed.IsEmpty then + printfn $"Undisposed instances: %A{undisposed}" + failwithf "Not all disposables were properly disposed" + else + printfn $"Success! All %d{constructed.Count} disposables were properly disposed" -let disposeCalls = Disposable.DisposeCallCount() -if disposeCalls <> 8 then - failwithf $"unexpected dispose call count: %i{disposeCalls}" -let ctorCalls = Disposable.ConstructorCallCount() -if ctorCalls <> 8 then - failwithf $"unexpected constructor call count: %i{ctorCalls}" \ No newline at end of file +testBindingPatterns() \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang02.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang02.fs new file mode 100644 index 00000000000..d57f0b8c14d --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang02.fs @@ -0,0 +1,57 @@ +open System + +open System + +type Disposable(id: int) = + static let mutable disposedIds = Set.empty + static let mutable constructedIds = Set.empty + + do constructedIds <- constructedIds.Add(id) + + member _.Id = id + + static member GetDisposed() = disposedIds + static member GetConstructed() = constructedIds + static member Reset() = + disposedIds <- Set.empty + constructedIds <- Set.empty + + interface IDisposable with + member this.Dispose() = disposedIds <- disposedIds.Add(this.Id) + +type DisposableBuilder() = + member _.Using(resource: #IDisposable, f) = + async { + use res = resource + return! f res + } + + member _.Bind(disposable: Disposable, f) = async.Bind(async.Return(disposable), f) + member _.Return(x) = async.Return x + member _.ReturnFrom(x) = x + member _.Bind(task, f) = async.Bind(task, f) + +let counterDisposable = DisposableBuilder() + +let testBindingPatterns() = + Disposable.Reset() + + counterDisposable { + use! _ = new Disposable(1) + use! _ = new Disposable(2) + use! (_) = new Disposable(3) + use! (_) = new Disposable(4) + return () + } |> Async.RunSynchronously + + let constructed = Disposable.GetConstructed() + let disposed = Disposable.GetDisposed() + let undisposed = constructed - disposed + + if not undisposed.IsEmpty then + printfn $"Undisposed instances: %A{undisposed}" + failwithf "Not all disposables were properly disposed" + else + printfn $"Success! All %d{constructed.Count} disposables were properly disposed" + +testBindingPatterns() \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang03.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang03.fs new file mode 100644 index 00000000000..57c814c561a --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang03.fs @@ -0,0 +1,71 @@ +open System + +type Disposable(id: int) = + static let mutable disposedIds = Map.empty + static let mutable constructedIds = Set.empty + + do constructedIds <- constructedIds.Add(id) + + member _.Id = id + + static member GetDisposed() = disposedIds + static member GetConstructed() = constructedIds + static member Reset() = + disposedIds <- Map.empty + constructedIds <- Set.empty + + interface IDisposable with + member this.Dispose() = + let currentCount = + match Map.tryFind this.Id disposedIds with + | Some count -> count + | None -> 0 + disposedIds <- Map.add this.Id (currentCount + 1) disposedIds + +type DisposableBuilder() = + member _.Using(resource: #IDisposable, f) = + async { + use res = resource + return! f res + } + + member _.Bind(disposable: Disposable, f) = async.Bind(async.Return(disposable), f) + member _.Return(x) = async.Return x + member _.ReturnFrom(x) = x + member _.Bind(task, f) = async.Bind(task, f) + +let counterDisposable = DisposableBuilder() + +let testBindingPatterns() = + Disposable.Reset() + + counterDisposable { + use! res = new Disposable(1) + use! __ = new Disposable(2) + use! (res1) = new Disposable(3) + use! _ = new Disposable(4) + use! (_) = new Disposable(5) + return () + } |> Async.RunSynchronously + + let constructed = Disposable.GetConstructed() + let disposed = Disposable.GetDisposed() + + let disposedSet = Set.ofSeq (Map.keys disposed) + let undisposed = constructed - disposedSet + + if not undisposed.IsEmpty then + printfn $"Undisposed instances: %A{undisposed}" + failwithf "Not all disposables were properly disposed" + + let multipleDisposed = + disposed + |> Map.filter (fun _ count -> count > 1) + + if not multipleDisposed.IsEmpty then + printfn $"Objects disposed multiple times: %A{multipleDisposed}" + failwithf "Some disposables were disposed multiple times" + + printfn $"Success! All %d{constructed.Count} disposables were properly disposed exactly once" + +testBindingPatterns() \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang04.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang04.fs new file mode 100644 index 00000000000..3c32e29bc46 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBang04.fs @@ -0,0 +1,91 @@ +open System + +type Disposable(id: int) = + static let mutable disposedIds = Map.empty + static let mutable constructedIds = Set.empty + + do constructedIds <- constructedIds.Add(id) + + member _.Id = id + + static member GetDisposed() = disposedIds + static member GetConstructed() = constructedIds + static member Reset() = + disposedIds <- Map.empty + constructedIds <- Set.empty + + interface IDisposable with + member this.Dispose() = + let currentCount = + match Map.tryFind this.Id disposedIds with + | Some count -> count + | None -> 0 + disposedIds <- Map.add this.Id (currentCount + 1) disposedIds + +type DisposableBuilder() = + member _.Using(resource: #IDisposable, f) = + async { + use res = resource + return! f res + } + + member _.Bind(disposable: Disposable, f) = async.Bind(async.Return(disposable), f) + member _.Return(x) = async.Return x + member _.ReturnFrom(x) = x + member _.Bind(task, f) = async.Bind(task, f) + +let counterDisposable = DisposableBuilder() + +let testBindingPatterns() = + Disposable.Reset() + + counterDisposable { + use! res = new Disposable(1) + use! __ = new Disposable(2) + use! (res1) = new Disposable(3) + + use! _ = new Disposable(4) + use! _ = new Disposable(5) + + use! x = new Disposable(6) + use! x = new Disposable(7) + + use! (_) = new Disposable(8) + use! (_) = new Disposable(9) + + return () + } |> Async.RunSynchronously + + let constructed = Disposable.GetConstructed() + let disposed = Disposable.GetDisposed() + + let disposedSet = Set.ofSeq (Map.keys disposed) + let undisposed = constructed - disposedSet + + if not undisposed.IsEmpty then + printfn $"Undisposed instances: %A{undisposed}" + failwithf "Not all disposables were properly disposed" + + // Verify each object was disposed exactly once + let incorrectlyDisposed = + disposed + |> Map.partition (fun _ count -> count = 1) + |> snd + + if not incorrectlyDisposed.IsEmpty then + printfn $"Objects with incorrect disposal count: %A{incorrectlyDisposed}" + failwithf "Some disposables were not disposed exactly once" + + let idChecks = + [1..9] |> List.map (fun id -> + match Map.tryFind id disposed with + | Some 1 -> true + | _ -> false + ) + + if not ((List.forall id) idChecks) then + failwithf "Not all disposable IDs were properly handled" + + printfn $"Success! All %d{constructed.Count} disposables were properly disposed exactly once, including repeated patterns" + +testBindingPatterns() \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBangBindings.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBangBindings.fs index c9f1b532f00..ba0f31bd885 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBangBindings.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBangBindings.fs @@ -17,6 +17,39 @@ module UseBangBindingsVersion9 = |> withDiagnostics [ (Error 1228, Line 43, Col 14, Line 43, Col 15, "'use!' bindings must be of the form 'use! = '") ] + + [] + let ``UseBangBindings - UseBang02_fs - Current LangVersion`` compilation = + compilation + |> asFsx + |> withLangVersion90 + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 1228, Line 40, Col 14, Line 40, Col 15, "'use!' bindings must be of the form 'use! = '") + ] + + [] + let ``UseBangBindings - UseBang03_fs - Current LangVersion`` compilation = + compilation + |> asFsx + |> withLangVersion90 + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 1228, Line 46, Col 14, Line 46, Col 15, "'use!' bindings must be of the form 'use! = '") + ] + + [] + let ``UseBangBindings - UseBang04_fs - Current LangVersion`` compilation = + compilation + |> asFsx + |> withLangVersion90 + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 1228, Line 47, Col 14, Line 47, Col 15, "'use!' bindings must be of the form 'use! = '") + ] module UseBangBindingsPreview = [] @@ -26,3 +59,28 @@ module UseBangBindingsPreview = |> withLangVersionPreview |> compileAndRun |> shouldSucceed + + [] + let ``UseBangBindings - UseBang02_fs - Preview LangVersion`` compilation = + compilation + |> asExe + |> withLangVersionPreview + |> compileAndRun + |> shouldSucceed + + [] + let ``UseBangBindings - UseBang03_fs - Preview LangVersion`` compilation = + compilation + |> asExe + |> withLangVersionPreview + |> compileAndRun + |> shouldSucceed + + [] + let ``UseBangBindings - UseBang04_fs - Preview LangVersion`` compilation = + compilation + |> asExe + |> withLangVersionPreview + |> compileAndRun + |> shouldSucceed + From 0c8a0a45944b6e7c24a5d24faa1fb84a2b6408b3 Mon Sep 17 00:00:00 2001 From: edgargonzalez Date: Thu, 24 Apr 2025 20:54:51 +0100 Subject: [PATCH 10/10] Update featureUseBangBindingValueDiscard --- src/Compiler/FSComp.txt | 2 +- src/Compiler/xlf/FSComp.txt.cs.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.de.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.es.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.fr.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.it.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.ja.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.ko.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.pl.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.ru.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.tr.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 4 ++-- 14 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index d1dad954b3b..aab4de2a554 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1798,4 +1798,4 @@ featureDontWarnOnUppercaseIdentifiersInBindingPatterns,"Don't warn on uppercase featureDeprecatePlacesWhereSeqCanBeOmitted,"Deprecate places where 'seq' can be omitted" featureSupportValueOptionsAsOptionalParameters,"Support ValueOption as valid type for optional member parameters" featureSupportWarnWhenUnitPassedToObjArg,"Warn when unit is passed to a member accepting `obj` argument, e.g. `Method(o:obj)` will warn if called via `Method()`." -featureUseBangBindingValueDiscard,"Use bang binding with value discard" +featureUseBangBindingValueDiscard,"Allows use! _ = ... in computation expressions" diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 491e8a205c2..adb2b01d334 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index ada82f801ce..7deaf880012 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index d968092fd27..e8090e5579e 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index ad43afb8a8a..25faaecb3a4 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 8abe6bcd59a..30d68a3330b 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 693b5af58bb..9f2709fa559 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index cd9028b19a7..2c80d361ef6 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 9b0d13da5da..0bd80d75a72 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 10920378dcc..c0e79885fed 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index f15fa632ad4..3a4610e93df 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 9e26ccb98db..6d1bee2476b 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 5b707c0ba2d..a60e3188af7 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 86b0c173425..3253c4e45f7 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -658,8 +658,8 @@ - Use bang binding with value discard - Use bang binding with value discard + Allows use! _ = ... in computation expressions + Allows use! _ = ... in computation expressions