Skip to content

Allow typed bindings(and!) in CE without parentheses #18682

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 32 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1799aaf
make attribute targets mismatches a warning and not an error.
edgarfgp Apr 23, 2025
55507e9
release notes
edgarfgp Apr 23, 2025
1738018
update tests
edgarfgp Apr 23, 2025
65f5bb6
Merge branch 'main' into fix-attr-targets
edgarfgp Apr 24, 2025
0c97b9d
Merge branch 'main' into fix-attr-targets
edgarfgp Apr 27, 2025
6f2b706
update baselines
edgarfgp Apr 29, 2025
e8f1bb0
Merge branch 'main' into fix-attr-targets
edgarfgp Apr 29, 2025
75d8f5e
Update baselines
edgarfgp Apr 29, 2025
4f2e97e
Merge branch 'fix-attr-targets' of github.com:edgarfgp/fsharp into fi…
edgarfgp Apr 29, 2025
63be5d5
Merge branch 'main' into fix-attr-targets
edgarfgp Apr 30, 2025
4248f2a
Move attribute form logic to an AP
edgarfgp Apr 30, 2025
cc96217
Merge branch 'main' into fix-attr-targets
edgarfgp May 1, 2025
e270b88
Merge branch 'main' into fix-attr-targets
edgarfgp May 1, 2025
e0cc65a
Merge branch 'main' into fix-attr-targets
edgarfgp May 2, 2025
1e29d58
Merge branch 'main' into fix-attr-targets
edgarfgp May 5, 2025
1470bf9
Merge branch 'main' into fix-attr-targets
edgarfgp May 6, 2025
8988215
Merge branch 'main' into fix-attr-targets
edgarfgp May 7, 2025
74712e8
Merge branch 'main' into fix-attr-targets
edgarfgp May 12, 2025
967c4a9
Merge branch 'main' of github.com:edgarfgp/fsharp
edgarfgp May 13, 2025
a30cef4
Merge branch 'dotnet:main' into main
edgarfgp May 22, 2025
5fa0480
Merge branch 'dotnet:main' into main
edgarfgp May 24, 2025
15e3d34
Merge branch 'dotnet:main' into main
edgarfgp May 29, 2025
b7ffcf8
Merge branch 'dotnet:main' into main
edgarfgp Jun 6, 2025
549f961
Add new parser rule and syntax tree tests
edgarfgp Jun 10, 2025
b3f4baa
Add CE test that uses let! and and!
edgarfgp Jun 10, 2025
98937bc
more syntax tree tests
edgarfgp Jun 10, 2025
a020505
more syntax tree tests
edgarfgp Jun 10, 2025
1ba22bf
Merge branch 'main' into allow-and-bang-typed-bindings
edgarfgp Jun 11, 2025
dce81de
more syntax tree tests
edgarfgp Jun 10, 2025
c921606
Merge branch 'allow-and-bang-typed-bindings' of github.com:edgarfgp/f…
edgarfgp Jun 11, 2025
86fedfd
Update LanguageFeature and release notes
edgarfgp Jun 11, 2025
aee26ec
update baselines
edgarfgp Jun 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/release-notes/.FSharp.Compiler.Service/10.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* Fix parsing errors using anonymous records and units of measures ([PR #18543](https://github.com/dotnet/fsharp/pull/18543))
* Fix parsing errors using anonymous records and code quotations ([PR #18603](https://github.com/dotnet/fsharp/pull/18603))
* Fixed: Allow `return`, `return!`, `yield`, `yield!` type annotations without parentheses ([PR #18533](https://github.com/dotnet/fsharp/pull/18533))
* Allow `let!` and `use!` type annotations without requiring parentheses ([PR #18508](https://github.com/dotnet/fsharp/pull/18508))
* Allow `let!`, `use!`, `and!` type annotations without requiring parentheses (([PR #18508](https://github.com/dotnet/fsharp/pull/18508) and [PR #18682](https://github.com/dotnet/fsharp/pull/18682)))
* Fix find all references for F# exceptions ([PR #18565](https://github.com/dotnet/fsharp/pull/18565))
* Shorthand lambda: fix completion for chained calls and analysis for unfinished expression ([PR #18560](https://github.com/dotnet/fsharp/pull/18560))
* Completion: fix previous namespace considered opened [PR #18609](https://github.com/dotnet/fsharp/pull/18609)
Expand Down
2 changes: 1 addition & 1 deletion docs/release-notes/.Language/preview.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Warn when `unit` is passed to an `obj`-typed argument ([PR #18330](https://github.com/dotnet/fsharp/pull/18330))
* Fix parsing errors using anonymous records and units of measures ([PR #18543](https://github.com/dotnet/fsharp/pull/18543))
* 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))
* Allow `let!` and `use!` type annotations without requiring parentheses. ([PR #18508](https://github.com/dotnet/fsharp/pull/18508))
* Allow `let!`, `use!`, `and!` type annotations without requiring parentheses (([PR #18508](https://github.com/dotnet/fsharp/pull/18508) and [PR #18682](https://github.com/dotnet/fsharp/pull/18682)))

### Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1794,7 +1794,7 @@ let rec TryTranslateComputationExpression
ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.UseBangBindingValueDiscard

let supportsTypedLetOrUseBang =
ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.AllowTypedLetOrUseBang
ceenv.cenv.g.langVersion.SupportsFeature LanguageFeature.AllowTypedLetUseAndBang

// use! x = ...
// use! (x) = ...
Expand Down
6 changes: 3 additions & 3 deletions src/Compiler/Facilities/LanguageFeatures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ type LanguageFeature =
| UseBangBindingValueDiscard
| BetterAnonymousRecordParsing
| ScopedNowarn
| AllowTypedLetOrUseBang
| AllowTypedLetUseAndBang

/// LanguageVersion management
type LanguageVersion(versionText) =
Expand Down Expand Up @@ -236,7 +236,7 @@ type LanguageVersion(versionText) =
LanguageFeature.UseBangBindingValueDiscard, previewVersion
LanguageFeature.BetterAnonymousRecordParsing, previewVersion
LanguageFeature.ScopedNowarn, previewVersion
LanguageFeature.AllowTypedLetOrUseBang, previewVersion
LanguageFeature.AllowTypedLetUseAndBang, previewVersion
]

static let defaultLanguageVersion = LanguageVersion("default")
Expand Down Expand Up @@ -402,7 +402,7 @@ type LanguageVersion(versionText) =
| LanguageFeature.UseBangBindingValueDiscard -> FSComp.SR.featureUseBangBindingValueDiscard ()
| LanguageFeature.BetterAnonymousRecordParsing -> FSComp.SR.featureBetterAnonymousRecordParsing ()
| LanguageFeature.ScopedNowarn -> FSComp.SR.featureScopedNowarn ()
| LanguageFeature.AllowTypedLetOrUseBang -> FSComp.SR.featureAllowLetOrUseBangTypeAnnotationWithoutParens ()
| LanguageFeature.AllowTypedLetUseAndBang -> FSComp.SR.featureAllowLetOrUseBangTypeAnnotationWithoutParens ()

/// Get a version string associated with the given feature.
static member GetFeatureVersionString feature =
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Facilities/LanguageFeatures.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ type LanguageFeature =
| UseBangBindingValueDiscard
| BetterAnonymousRecordParsing
| ScopedNowarn
| AllowTypedLetOrUseBang
| AllowTypedLetUseAndBang

/// LanguageVersion management
type LanguageVersion =
Expand Down
44 changes: 38 additions & 6 deletions src/Compiler/pars.fsy
Original file line number Diff line number Diff line change
Expand Up @@ -4107,6 +4107,22 @@ moreBinders:
let trivia = { AndBangKeyword = rhs parseState 1; EqualsRange = mEquals; InKeyword = Some mIn }
SynExprAndBang(spBind, $1, true, $2, $4, m, trivia) :: $6 }

| AND_BANG headBindingPattern opt_topReturnTypeWithTypeConstraints EQUALS typedSequentialExprBlock IN moreBinders %prec expr_let
{ // Handle type annotations on patterns in and! bindings
// Example: and! y: string = asyncString()
let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 6)
let pat =
match $3 with
| None -> $2
| Some (_, SynReturnInfo((ty, _), _)) ->
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang ty.Range
SynPat.Typed($2, ty, unionRanges $2.Range ty.Range)
let mEquals = rhs parseState 4
let m = unionRanges (rhs parseState 1) $5.Range
let mIn = rhs parseState 6
let trivia = { AndBangKeyword = rhs parseState 1; EqualsRange = mEquals; InKeyword = Some mIn }
SynExprAndBang(spBind, false, true, pat, $5, m, trivia) :: $7 }

| OAND_BANG headBindingPattern EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP moreBinders %prec expr_let
{ let report, mIn, _ = $5
report "and!" (rhs parseState 1) // report unterminated error
Expand All @@ -4116,6 +4132,22 @@ moreBinders:
let trivia = { AndBangKeyword = rhs parseState 1; EqualsRange = mEquals; InKeyword = mIn }
SynExprAndBang(spBind, $1, true, $2, $4, m, trivia) :: $7 }

| OAND_BANG headBindingPattern opt_topReturnTypeWithTypeConstraints EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP moreBinders %prec expr_let
{ // Handle type annotations on patterns in and! bindings (offside-sensitive version)
let report, mIn, _ = $6
report "and!" (rhs parseState 1) // report unterminated error
let spBind = DebugPointAtBinding.Yes(unionRanges (rhs parseState 1) $5.Range)
let pat =
match $3 with
| None -> $2
| Some (_, SynReturnInfo((ty, _), _)) ->
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang ty.Range
SynPat.Typed($2, ty, unionRanges $2.Range ty.Range)
let mEquals = rhs parseState 4
let m = unionRanges (rhs parseState 1) $5.Range
let trivia = { AndBangKeyword = rhs parseState 1; EqualsRange = mEquals; InKeyword = mIn }
SynExprAndBang(spBind, false, true, pat, $5, m, trivia) :: $8 }

| %prec prec_no_more_attr_bindings
{ [] }

Expand Down Expand Up @@ -4429,7 +4461,7 @@ declExpr:
| YIELD declExpr COLON typ
{ let trivia: SynExprYieldOrReturnTrivia = { YieldOrReturnKeyword = rhs parseState 1 }
let typedExpr = SynExpr.Typed($2, $4, unionRanges $2.Range $4.Range)
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetOrUseBang typedExpr.Range
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang typedExpr.Range
SynExpr.YieldOrReturn(($1, not $1), typedExpr, (unionRanges (rhs parseState 1) $4.Range), trivia) }

| YIELD declExpr opt_topReturnTypeWithTypeConstraints
Expand All @@ -4439,7 +4471,7 @@ declExpr:
| None -> $2
| Some(_, SynReturnInfo((ty, _), m)) ->
let m = unionRanges $2.Range m
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetOrUseBang m
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang m
SynExpr.Typed($2, ty, m)
SynExpr.YieldOrReturn(($1, not $1), expr, (unionRanges (rhs parseState 1) expr.Range), trivia) }

Expand All @@ -4456,7 +4488,7 @@ declExpr:
| YIELD_BANG declExpr COLON typ
{ let trivia: SynExprYieldOrReturnFromTrivia = { YieldOrReturnFromKeyword = rhs parseState 1 }
let typedExpr = SynExpr.Typed($2, $4, unionRanges $2.Range $4.Range)
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetOrUseBang typedExpr.Range
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang typedExpr.Range
SynExpr.YieldOrReturnFrom(($1, not $1), typedExpr, (unionRanges (rhs parseState 1) $2.Range), trivia) }

| YIELD_BANG declExpr opt_topReturnTypeWithTypeConstraints
Expand All @@ -4466,7 +4498,7 @@ declExpr:
| None -> $2
| Some(_, SynReturnInfo((ty, _), m)) ->
let m = unionRanges $2.Range m
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetOrUseBang m
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang m
SynExpr.Typed($2, ty, m)
SynExpr.YieldOrReturnFrom(($1, not $1), expr, (unionRanges (rhs parseState 1) $2.Range), trivia) }

Expand Down Expand Up @@ -4502,7 +4534,7 @@ declExpr:
| None -> $2
| Some (_, SynReturnInfo((ty, _), _)) ->
SynPat.Typed($2, ty, unionRanges $2.Range ty.Range)
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetOrUseBang pat.Range
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang pat.Range
let mEquals = rhs parseState 4
let m = unionRanges (rhs parseState 1) $9.Range
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
Expand All @@ -4519,7 +4551,7 @@ declExpr:
| None -> $2
| Some (_, SynReturnInfo((ty, _), _)) ->
SynPat.Typed($2, ty, unionRanges $2.Range ty.Range)
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetOrUseBang pat.Range
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang pat.Range
let mEquals = rhs parseState 4
let m = unionRanges (rhs parseState 1) $9.Range
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1521,4 +1521,122 @@ let fooTask () : Task<int64> = task {
is not compatible with type
'TaskCode<int64,int64>'
")
]
]

[<Fact>]
let ``Version 9.0: and! with type annotations requires parentheses`` () =
FSharp """
module Test

type ParallelBuilder() =
member _.Return(x) = async { return x }
member _.ReturnFrom(computation: Async<'T>) = computation
member _.Bind(computation: Async<'T>, binder: 'T -> Async<'U>) =
async {
let! x = computation
return! binder x
}
member _.Bind2(comp1: Async<'T1>, comp2: Async<'T2>, binder: 'T1 * 'T2 -> Async<'U>) =
async {
let! task1 = Async.StartChild comp1
let! task2 = Async.StartChild comp2
let! result1 = task1
let! result2 = task2
return! binder (result1, result2)
}

member _.Zero() = async.Zero()
member _.Combine(comp1, comp2) = async.Combine(comp1, comp2)
member _.Delay(f) = async.Delay(f)

let parallelCE = ParallelBuilder()

let testParallel() =
parallelCE {
let! x = async { return 1 }
and! y = async { return 2 }
return x + y
}

let testParallel2() =
parallelCE {
let! (x: int) = async { return 1 }
and! (y: int) = async { return 2 }
return x + y
}

let testParallel3() =
parallelCE {
let! x: int = async { return 1 }
and! y: int = async { return 2 }
return x + y
}
"""
|> withLangVersion90
|> typecheck
|> shouldFail
|> withDiagnostics [
(Error 3350, Line 44, Col 17, Line 44, Col 20, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.");
(Error 3350, Line 43, Col 14, Line 43, Col 20, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.")
]

[<Fact>]
let ``Preview: and! with type annotations works without parentheses`` () =
FSharp """
module Test

type ParallelBuilder() =
member _.Return(x) = async { return x }
member _.ReturnFrom(computation: Async<'T>) = computation
member _.Bind(computation: Async<'T>, binder: 'T -> Async<'U>) =
async {
let! x = computation
return! binder x
}
member _.Bind2(comp1: Async<'T1>, comp2: Async<'T2>, binder: 'T1 * 'T2 -> Async<'U>) =
async {
let! task1 = Async.StartChild comp1
let! task2 = Async.StartChild comp2
let! result1 = task1
let! result2 = task2
return! binder (result1, result2)
}

member _.Zero() = async.Zero()
member _.Combine(comp1, comp2) = async.Combine(comp1, comp2)
member _.Delay(f) = async.Delay(f)

let parallelCE = ParallelBuilder()

let testParallel() =
parallelCE {
let! x = async { return 1 }
and! y = async { return 2 }
return x + y
}

let testParallel2() =
parallelCE {
let! (x: int) = async { return 1 }
and! (y: int) = async { return 2 }
return x + y
}

let testParallel3() =
parallelCE {
let! x: int = async { return 1 }
and! y: int = async { return 2 }
return x + y
}

let result = testParallel() |> Async.RunSynchronously
let result2 = testParallel2() |> Async.RunSynchronously
let result3 = testParallel3() |> Async.RunSynchronously
if result <> 3 then failwithf $"Expected 3, but got {result}"
if result2 <> 3 then failwithf $"Expected 3, but got {result2}"
if result3 <> 3 then failwithf $"Expected 3, but got {result3}"
"""
|> withLangVersionPreview
|> asExe
|> compileAndRun
|> shouldSucceed
3 changes: 3 additions & 0 deletions tests/service/data/SyntaxTree/SynType/Tuple 15.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module Module

let x: int, y: int = 0, 4
31 changes: 31 additions & 0 deletions tests/service/data/SyntaxTree/SynType/Tuple 15.fs.bsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
ImplFile
(ParsedImplFileInput
("/root/SynType/Tuple 15.fs", false, QualifiedNameOfFile Module, [],
[SynModuleOrNamespace
([Module], false, NamedModule,
[Let
(false,
[SynBinding
(None, Normal, false, false, [],
PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector),
SynValData
(None, SynValInfo ([], SynArgInfo ([], false, None)), None),
Named (SynIdent (x, None), false, None, (3,4--3,5)),
Some
(SynBindingReturnInfo
(LongIdent (SynLongIdent ([int], [], [None])),
(3,7--3,10), [], { ColonRange = Some (3,5--3,6) })),
Typed
(ArbitraryAfterError ("localBinding2", (3,10--3,10)),
LongIdent (SynLongIdent ([int], [], [None])), (3,10--3,10)),
(3,4--3,5), Yes (3,0--3,10), { LeadingKeyword = Let (3,0--3,3)
InlineKeyword = None
EqualsRange = None })],
(3,0--3,10))],
PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None,
(1,0--3,10), { LeadingKeyword = Module (1,0--1,6) })], (true, true),
{ ConditionalDirectives = []
WarnDirectives = []
CodeComments = [] }, set []))

(3,10)-(3,11) parse error Unexpected symbol ',' in binding. Expected '=' or other token.
7 changes: 7 additions & 0 deletions tests/service/data/SyntaxTree/SynType/Typed LetBang 07.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Module
async {
let! ({ Name = name }: Person) = asyncPerson()

return name

}
35 changes: 35 additions & 0 deletions tests/service/data/SyntaxTree/SynType/Typed LetBang 07.fs.bsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
ImplFile
(ParsedImplFileInput
("/root/SynType/Typed LetBang 07.fs", false, QualifiedNameOfFile Module, [],
[SynModuleOrNamespace
([Module], false, NamedModule,
[Expr
(App
(NonAtomic, false, Ident async,
ComputationExpr
(false,
LetOrUseBang
(Yes (3,4--3,50), false, true,
Paren
(Typed
(Record
([(([], Name), Some (3,17--3,18),
Named
(SynIdent (name, None), false, None,
(3,19--3,23)))], (3,10--3,25)),
LongIdent (SynLongIdent ([Person], [], [None])),
(3,10--3,33)), (3,9--3,34)),
App
(Atomic, false, Ident asyncPerson,
Const (Unit, (3,48--3,50)), (3,37--3,50)), [],
YieldOrReturn
((false, true), Ident name, (5,4--5,15),
{ YieldOrReturnKeyword = (5,4--5,10) }), (3,4--5,15),
{ LetOrUseBangKeyword = (3,4--3,8)
EqualsRange = Some (3,35--3,36) }), (2,6--7,1)),
(2,0--7,1)), (2,0--7,1))],
PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), [], None,
(1,0--7,1), { LeadingKeyword = Module (1,0--1,6) })], (true, true),
{ ConditionalDirectives = []
WarnDirectives = []
CodeComments = [] }, set []))
6 changes: 6 additions & 0 deletions tests/service/data/SyntaxTree/SynType/Typed LetBang 08.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Module
async {
let! { Name = name }: Person = asyncPerson()
return name

}
Loading
Loading