Skip to content

Commit 8b7bf60

Browse files
authored
Allow let! and use! binding with type annotation without parentheses. (#18508)
1 parent 5db953f commit 8b7bf60

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+836
-1
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Fixed
2+
3+
* Allow `let!` and `use!` type annotations without requiring parentheses ([PR #18508](https://github.com/dotnet/fsharp/pull/18508))

docs/release-notes/.Language/preview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* Allow `_` in `use!` bindings values (lift FS1228 restriction) ([PR #18487](https://github.com/dotnet/fsharp/pull/18487))
88
* Warn when `unit` is passed to an `obj`-typed argument ([PR #18330](https://github.com/dotnet/fsharp/pull/18330))
99
* Scoped Nowarn: added the #warnon compiler directive ([Language suggestion #278](https://github.com/fsharp/fslang-suggestions/issues/278), [RFC FS-1146 PR](https://github.com/fsharp/fslang-design/pull/782), [PR #18049](https://github.com/dotnet/fsharp/pull/18049))
10+
* Allow `let!` and `use!` type annotations without requiring parentheses. ([PR #18508](https://github.com/dotnet/fsharp/pull/18508))
1011

1112
### Fixed
1213

src/Compiler/Checking/Expressions/CheckComputationExpressions.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1793,6 +1793,9 @@ let rec TryTranslateComputationExpression
17931793
let supportsUseBangBindingValueDiscard =
17941794
ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.UseBangBindingValueDiscard
17951795

1796+
let supportsTypedLetOrUseBang =
1797+
ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.AllowTypedLetOrUseBang
1798+
17961799
// use! x = ...
17971800
// use! (x) = ...
17981801
// use! (__) = ...
@@ -1802,6 +1805,7 @@ let rec TryTranslateComputationExpression
18021805
match pat with
18031806
| SynPat.Named(ident = SynIdent(id, _); isThisVal = false) -> id, pat
18041807
| SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ])) -> id, pat
1808+
| SynPat.Typed(pat = pat) when supportsTypedLetOrUseBang -> extractIdentifierFromPattern pat
18051809
| SynPat.Wild(m) when supportsUseBangBindingValueDiscard ->
18061810
// To properly call the Using(disposable) CE member, we need to convert the wildcard to a SynPat.Named
18071811
let tmpIdent = mkSynId m "_"

src/Compiler/FSComp.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1800,6 +1800,7 @@ featureSupportValueOptionsAsOptionalParameters,"Support ValueOption as valid typ
18001800
featureSupportWarnWhenUnitPassedToObjArg,"Warn when unit is passed to a member accepting `obj` argument, e.g. `Method(o:obj)` will warn if called via `Method()`."
18011801
featureUseBangBindingValueDiscard,"Allows use! _ = ... in computation expressions"
18021802
featureScopedNowarn,"Support for scoped enabling / disabling of warnings by #warn and #nowarn directives, also inside modules"
1803+
featureAllowLetOrUseBangTypeAnnotationWithoutParens,"Allow let! and use! type annotations without requiring parentheses"
18031804
3874,lexWarnDirectiveMustBeFirst,"#nowarn/#warnon directives must appear as the first non-whitespace characters on a line"
18041805
3875,lexWarnDirectiveMustHaveArgs,"Warn directives must have warning number(s) as argument(s)"
18051806
3876,lexWarnDirectivesMustMatch,"There is another %s for this warning already in line %d."

src/Compiler/Facilities/LanguageFeatures.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ type LanguageFeature =
101101
| WarnWhenUnitPassedToObjArg
102102
| UseBangBindingValueDiscard
103103
| ScopedNowarn
104+
| AllowTypedLetOrUseBang
104105

105106
/// LanguageVersion management
106107
type LanguageVersion(versionText) =
@@ -233,6 +234,7 @@ type LanguageVersion(versionText) =
233234
LanguageFeature.WarnWhenUnitPassedToObjArg, previewVersion
234235
LanguageFeature.UseBangBindingValueDiscard, previewVersion
235236
LanguageFeature.ScopedNowarn, previewVersion
237+
LanguageFeature.AllowTypedLetOrUseBang, previewVersion
236238
]
237239

238240
static let defaultLanguageVersion = LanguageVersion("default")
@@ -397,6 +399,7 @@ type LanguageVersion(versionText) =
397399
| LanguageFeature.WarnWhenUnitPassedToObjArg -> FSComp.SR.featureSupportWarnWhenUnitPassedToObjArg ()
398400
| LanguageFeature.UseBangBindingValueDiscard -> FSComp.SR.featureUseBangBindingValueDiscard ()
399401
| LanguageFeature.ScopedNowarn -> FSComp.SR.featureScopedNowarn ()
402+
| LanguageFeature.AllowTypedLetOrUseBang -> FSComp.SR.featureAllowLetOrUseBangTypeAnnotationWithoutParens ()
400403

401404
/// Get a version string associated with the given feature.
402405
static member GetFeatureVersionString feature =

src/Compiler/Facilities/LanguageFeatures.fsi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ type LanguageFeature =
9292
| WarnWhenUnitPassedToObjArg
9393
| UseBangBindingValueDiscard
9494
| ScopedNowarn
95+
| AllowTypedLetOrUseBang
9596

9697
/// LanguageVersion management
9798
type LanguageVersion =

src/Compiler/pars.fsy

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4466,6 +4466,39 @@ declExpr:
44664466
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
44674467
SynExpr.LetOrUseBang(spBind, ($1 = "use"), true, $2, $4, $7, $8, m, trivia) }
44684468

4469+
| BINDER headBindingPattern opt_topReturnTypeWithTypeConstraints EQUALS typedSequentialExprBlock IN opt_OBLOCKSEP moreBinders typedSequentialExprBlock %prec expr_let
4470+
{ // Handle type annotations on patterns in let!/use! bindings
4471+
// Examples: let! x: int = async { return 1 }
4472+
// use! _: IDisposable = async { return new MemoryStream() }
4473+
let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 7)
4474+
let pat =
4475+
match $3 with
4476+
| None -> $2
4477+
| Some (_, SynReturnInfo((ty, _), _)) ->
4478+
SynPat.Typed($2, ty, unionRanges $2.Range ty.Range)
4479+
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetOrUseBang pat.Range
4480+
let mEquals = rhs parseState 4
4481+
let m = unionRanges (rhs parseState 1) $9.Range
4482+
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
4483+
SynExpr.LetOrUseBang(spBind, ($1 = "use"), true, pat, $5, $8, $9, m, trivia) }
4484+
4485+
| OBINDER headBindingPattern opt_topReturnTypeWithTypeConstraints EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP moreBinders typedSequentialExprBlock %prec expr_let
4486+
{ // Handle type annotations on patterns in let!/use! bindings (offside-sensitive version)
4487+
// This rule maintains consistent handling of binding constructs across different syntactic contexts
4488+
let report, mIn, _ = $6
4489+
report (if $1 = "use" then "use!" else "let!") (rhs parseState 1) // report unterminated error
4490+
let spBind = DebugPointAtBinding.Yes(unionRanges (rhs parseState 1) $5.Range)
4491+
let pat =
4492+
match $3 with
4493+
| None -> $2
4494+
| Some (_, SynReturnInfo((ty, _), _)) ->
4495+
SynPat.Typed($2, ty, unionRanges $2.Range ty.Range)
4496+
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetOrUseBang pat.Range
4497+
let mEquals = rhs parseState 4
4498+
let m = unionRanges (rhs parseState 1) $9.Range
4499+
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
4500+
SynExpr.LetOrUseBang(spBind, ($1 = "use"), true, pat, $5, $8, $9, m, trivia) }
4501+
44694502
| OBINDER headBindingPattern EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP error %prec expr_let
44704503
{ // error recovery that allows intellisense when writing incomplete computation expressions
44714504
let spBind = DebugPointAtBinding.Yes(unionRanges (rhs parseState 1) $4.Range)

src/Compiler/xlf/FSComp.txt.cs.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.de.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.es.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.fr.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.it.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.ja.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.ko.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.pl.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.pt-BR.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.ru.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.tr.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.zh-Hans.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.zh-Hant.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
open System
2+
3+
open System
4+
5+
type Disposable(id: int) =
6+
static let mutable disposedIds = Set.empty<int>
7+
static let mutable constructedIds = Set.empty<int>
8+
9+
do constructedIds <- constructedIds.Add(id)
10+
11+
member _.Id = id
12+
13+
static member GetDisposed() = disposedIds
14+
static member GetConstructed() = constructedIds
15+
static member Reset() =
16+
disposedIds <- Set.empty
17+
constructedIds <- Set.empty
18+
19+
interface IDisposable with
20+
member this.Dispose() = disposedIds <- disposedIds.Add(this.Id)
21+
22+
type DisposableBuilder() =
23+
member _.Using(resource: #IDisposable, f) =
24+
async {
25+
use res = resource
26+
return! f res
27+
}
28+
29+
member _.Bind(disposable: Disposable, f) = async.Bind(async.Return(disposable), f)
30+
member _.Return(x) = async.Return x
31+
member _.ReturnFrom(x) = x
32+
member _.Bind(task, f) = async.Bind(task, f)
33+
34+
let counterDisposable = DisposableBuilder()
35+
36+
let testBindingPatterns() =
37+
Disposable.Reset()
38+
39+
counterDisposable {
40+
use! res:IDisposable = new Disposable(1)
41+
use! __:IDisposable = new Disposable(2)
42+
use! (res1: IDisposable) = new Disposable(3)
43+
use! _: IDisposable = new Disposable(4)
44+
use! (_: IDisposable) = new Disposable(5)
45+
return ()
46+
} |> Async.RunSynchronously
47+
48+
let constructed = Disposable.GetConstructed()
49+
let disposed = Disposable.GetDisposed()
50+
let undisposed = constructed - disposed
51+
52+
if not undisposed.IsEmpty then
53+
printfn $"Undisposed instances: %A{undisposed}"
54+
failwithf "Not all disposables were properly disposed"
55+
else
56+
printfn $"Success! All %d{constructed.Count} disposables were properly disposed"
57+
58+
testBindingPatterns()

tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/UseBindings/UseBangBindings.fs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ module UseBangBindingsVersion9 =
5050
|> withDiagnostics [
5151
(Error 1228, Line 47, Col 14, Line 47, Col 15, "'use!' bindings must be of the form 'use! <var> = <expr>'")
5252
]
53+
54+
[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"UseBang05.fs"|])>]
55+
let ``UseBangBindings - UseBang05_fs - Current LangVersion`` compilation =
56+
compilation
57+
|> asFsx
58+
|> withLangVersion90
59+
|> typecheck
60+
|> shouldFail
61+
|> withDiagnostics [
62+
(Error 3350, Line 43, Col 14, Line 43, Col 28, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.")
63+
(Error 3350, Line 41, Col 14, Line 41, Col 28, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.")
64+
(Error 3350, Line 40, Col 14, Line 40, Col 29, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.")
65+
]
5366

5467
module UseBangBindingsPreview =
5568
[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"UseBang01.fs"|])>]
@@ -83,4 +96,13 @@ module UseBangBindingsPreview =
8396
|> withLangVersionPreview
8497
|> compileAndRun
8598
|> shouldSucceed
99+
100+
[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"UseBang05.fs"|])>]
101+
let ``UseBangBindings - UseBang05_fs - Preview LangVersion`` compilation =
102+
compilation
103+
|> asExe
104+
|> withLangVersionPreview
105+
|> compileAndRun
106+
|> shouldSucceed
107+
86108

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// #DeclarationElements #LetBindings
2+
//<Expects status="success"></Expects>
3+
open System
4+
5+
let answer =
6+
use x:IDisposable = new System.IO.MemoryStream()
7+
42

0 commit comments

Comments
 (0)