Skip to content

Commit 11a44ca

Browse files
committed
Unifying let, let! and! parser rules
1 parent 3a346de commit 11a44ca

File tree

45 files changed

+669
-440
lines changed

Some content is hidden

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

45 files changed

+669
-440
lines changed

src/Compiler/pars.fsy

Lines changed: 129 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3520,6 +3520,57 @@ bindingPattern:
35203520
| headBindingPattern
35213521
{ $1, $1.Range }
35223522

3523+
/* Common pattern for bindings that may include type annotations
3524+
This rule unifies the pattern parsing for both regular 'let' bindings and
3525+
computation expression bindings (let!, use!, and!).
3526+
3527+
Returns: (pattern, pattern_range, optional_type)
3528+
- pattern: The parsed pattern (may be typed if annotation present)
3529+
- pattern_range: The full range including any type annotation
3530+
- optional_type: The type if annotation was present, None otherwise */
3531+
bindingPatternWithOptType:
3532+
| headBindingPattern
3533+
{ // Simple pattern without type annotation
3534+
$1, $1.Range, None }
3535+
3536+
| headBindingPattern COLON topTypeWithTypeConstraints
3537+
{ // Pattern with type annotation (e.g., x: int)
3538+
let ty, arity = $3
3539+
let mWhole = unionRanges $1.Range ty.Range
3540+
// Create a typed pattern node
3541+
let typedPat = SynPat.Typed($1, ty, mWhole)
3542+
typedPat, mWhole, Some ty }
3543+
3544+
| headBindingPattern COLON error
3545+
{ // Error recovery: incomplete type annotation (e.g., "let x:" with no type)
3546+
let mColon = rhs parseState 2
3547+
let mWhole = unionRanges $1.Range mColon
3548+
// Create a typed pattern with an error type for recovery
3549+
let errorType = SynType.FromParseError(mColon.EndRange)
3550+
let typedPat = SynPat.Typed($1, errorType, mWhole)
3551+
typedPat, mWhole, Some errorType }
3552+
3553+
/* Common rule for computation expression binding patterns
3554+
Handles the pattern part of let!, use!, and! bindings with consistent
3555+
type annotation support and language feature checking.
3556+
3557+
Returns: (pattern, pattern_range, isInline, isMutable, optional_type) */
3558+
ceBindingCore:
3559+
| opt_inline opt_mutable bindingPatternWithOptType
3560+
{ let pat, mPat, tyOpt = $3
3561+
let isInline = Option.isSome $1
3562+
let isMutable = Option.isSome $2
3563+
3564+
// For CE bindings, check language feature if type annotation is present
3565+
// This ensures that typed let!/use!/and! bindings are only allowed when
3566+
// the AllowTypedLetUseAndBang feature is enabled
3567+
match tyOpt with
3568+
| Some ty ->
3569+
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang ty.Range
3570+
| None -> ()
3571+
3572+
pat, mPat, isInline, isMutable, tyOpt }
3573+
35233574
opt_simplePatterns:
35243575
| simplePatterns
35253576
{ Some $1 }
@@ -4099,54 +4150,45 @@ recover:
40994150
{ debugPrint("recovering via EOF"); false }
41004151

41014152
moreBinders:
4102-
| AND_BANG headBindingPattern EQUALS typedSequentialExprBlock IN moreBinders %prec expr_let
4103-
{ let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 5) (* TODO Pretty sure this is wrong *)
4153+
/* Refactored and! bindings to use unified ceBindingCore
4154+
This ensures consistent handling of patterns and type annotations
4155+
across all computation expression bindings */
4156+
| AND_BANG ceBindingCore EQUALS typedSequentialExprBlock IN moreBinders %prec expr_let
4157+
{ // Handle and! bindings with unified pattern parsing
4158+
let pat, mPat, isInline, isMutable, tyOpt = $2
4159+
4160+
// and! bindings don't support inline or mutable modifiers
4161+
if isInline then errorR(Error(FSComp.SR.parsInvalidDeclarationSyntax(), rhs parseState 2))
4162+
if isMutable then errorR(Error(FSComp.SR.parsInvalidDeclarationSyntax(), rhs parseState 2))
4163+
41044164
let mEquals = rhs parseState 3
41054165
let m = unionRanges (rhs parseState 1) $4.Range
4166+
// Debug point should span the entire binding: from AND_BANG through the expression
4167+
let spBind = DebugPointAtBinding.Yes(m)
41064168
let mIn = rhs parseState 5
41074169
let trivia = { AndBangKeyword = rhs parseState 1; EqualsRange = mEquals; InKeyword = Some mIn }
4108-
SynExprAndBang(spBind, $1, true, $2, $4, m, trivia) :: $6 }
4109-
4110-
| AND_BANG headBindingPattern opt_topReturnTypeWithTypeConstraints EQUALS typedSequentialExprBlock IN moreBinders %prec expr_let
4111-
{ // Handle type annotations on patterns in and! bindings
4112-
// Example: and! y: string = asyncString()
4113-
let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 6)
4114-
let pat =
4115-
match $3 with
4116-
| None -> $2
4117-
| Some (_, SynReturnInfo((ty, _), _)) ->
4118-
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang ty.Range
4119-
SynPat.Typed($2, ty, unionRanges $2.Range ty.Range)
4120-
let mEquals = rhs parseState 4
4121-
let m = unionRanges (rhs parseState 1) $5.Range
4122-
let mIn = rhs parseState 6
4123-
let trivia = { AndBangKeyword = rhs parseState 1; EqualsRange = mEquals; InKeyword = Some mIn }
4124-
SynExprAndBang(spBind, false, true, pat, $5, m, trivia) :: $7 }
4125-
4126-
| OAND_BANG headBindingPattern EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP moreBinders %prec expr_let
4127-
{ let report, mIn, _ = $5
4170+
4171+
// Note: For and!, we always use isRecursive=false and isUse=true
4172+
SynExprAndBang(spBind, false, true, pat, $4, m, trivia) :: $6 }
4173+
4174+
| OAND_BANG ceBindingCore EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP moreBinders %prec expr_let
4175+
{ // Offside-sensitive version of and! binding
4176+
let pat, mPat, isInline, isMutable, tyOpt = $2
4177+
4178+
// and! bindings don't support inline or mutable modifiers
4179+
if isInline then errorR(Error(FSComp.SR.parsInvalidDeclarationSyntax(), rhs parseState 2))
4180+
if isMutable then errorR(Error(FSComp.SR.parsInvalidDeclarationSyntax(), rhs parseState 2))
4181+
4182+
let report, mIn, _ = $5
41284183
report "and!" (rhs parseState 1) // report unterminated error
4129-
let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 5) (* TODO Pretty sure this is wrong *)
41304184
let mEquals = rhs parseState 3
41314185
let m = unionRanges (rhs parseState 1) $4.Range
4186+
// Debug point should span the entire binding: from OAND_BANG through the expression
4187+
let spBind = DebugPointAtBinding.Yes(m)
41324188
let trivia = { AndBangKeyword = rhs parseState 1; EqualsRange = mEquals; InKeyword = mIn }
4133-
SynExprAndBang(spBind, $1, true, $2, $4, m, trivia) :: $7 }
4134-
4135-
| OAND_BANG headBindingPattern opt_topReturnTypeWithTypeConstraints EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP moreBinders %prec expr_let
4136-
{ // Handle type annotations on patterns in and! bindings (offside-sensitive version)
4137-
let report, mIn, _ = $6
4138-
report "and!" (rhs parseState 1) // report unterminated error
4139-
let spBind = DebugPointAtBinding.Yes(unionRanges (rhs parseState 1) $5.Range)
4140-
let pat =
4141-
match $3 with
4142-
| None -> $2
4143-
| Some (_, SynReturnInfo((ty, _), _)) ->
4144-
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang ty.Range
4145-
SynPat.Typed($2, ty, unionRanges $2.Range ty.Range)
4146-
let mEquals = rhs parseState 4
4147-
let m = unionRanges (rhs parseState 1) $5.Range
4148-
let trivia = { AndBangKeyword = rhs parseState 1; EqualsRange = mEquals; InKeyword = mIn }
4149-
SynExprAndBang(spBind, false, true, pat, $5, m, trivia) :: $8 }
4189+
4190+
// Note: For and!, we always use isRecursive=false and isUse=true
4191+
SynExprAndBang(spBind, false, true, pat, $4, m, trivia) :: $7 }
41504192

41514193
| %prec prec_no_more_attr_bindings
41524194
{ [] }
@@ -4502,69 +4544,65 @@ declExpr:
45024544
SynExpr.Typed($2, ty, m)
45034545
SynExpr.YieldOrReturnFrom(($1, not $1), expr, (unionRanges (rhs parseState 1) $2.Range), trivia) }
45044546

4505-
| BINDER headBindingPattern EQUALS typedSequentialExprBlock IN opt_OBLOCKSEP moreBinders typedSequentialExprBlock %prec expr_let
4506-
{ (* This rule handles the 'use!' and 'let!' binding expressions in computation expressions *)
4507-
(* The BINDER token represents keywords like 'use!' or 'let!' *)
4508-
(* headBindingPattern represents patterns in the binding:
4509-
- Underscore ('_') patterns are preserved as SynPat.Wild
4510-
- Named patterns ('__') are represented as SynPat.Named
4511-
- Identifiers (like 'value') are represented as SynPat.LongIdent *)
4512-
let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 5)
4547+
/* Refactored let! and use! bindings to use unified ceBindingCore
4548+
This ensures consistent handling across all computation expression bindings
4549+
while maintaining backward compatibility */
4550+
| BINDER ceBindingCore EQUALS typedSequentialExprBlock IN opt_OBLOCKSEP moreBinders typedSequentialExprBlock %prec expr_let
4551+
{ // Handle let! and use! bindings with unified pattern parsing
4552+
let pat, mPat, isInline, isMutable, tyOpt = $2
4553+
4554+
// let! and use! bindings don't support inline or mutable modifiers
4555+
if isInline then errorR(Error(FSComp.SR.parsInvalidDeclarationSyntax(), rhs parseState 2))
4556+
if isMutable then errorR(Error(FSComp.SR.parsInvalidDeclarationSyntax(), rhs parseState 2))
4557+
45134558
let mEquals = rhs parseState 3
45144559
let m = unionRanges (rhs parseState 1) $8.Range
4560+
// Debug point should span from BINDER through the expression (but not the continuation)
4561+
let spBind = DebugPointAtBinding.Yes(unionRanges (rhs parseState 1) $4.Range)
45154562
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
4516-
SynExpr.LetOrUseBang(spBind, ($1 = "use"), true, $2, $4, $7, $8, m, trivia) }
4517-
4518-
| OBINDER headBindingPattern EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP moreBinders typedSequentialExprBlock %prec expr_let
4519-
{ let report, mIn, _ = $5
4563+
4564+
// $1 contains the actual keyword ("let!" or "use!")
4565+
let isUse = ($1 = "use")
4566+
SynExpr.LetOrUseBang(spBind, isUse, true, pat, $4, $7, $8, m, trivia) }
4567+
4568+
| OBINDER ceBindingCore EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP moreBinders typedSequentialExprBlock %prec expr_let
4569+
{ // Offside-sensitive version of let!/use! binding
4570+
let pat, mPat, isInline, isMutable, tyOpt = $2
4571+
4572+
// let! and use! bindings don't support inline or mutable modifiers
4573+
if isInline then errorR(Error(FSComp.SR.parsInvalidDeclarationSyntax(), rhs parseState 2))
4574+
if isMutable then errorR(Error(FSComp.SR.parsInvalidDeclarationSyntax(), rhs parseState 2))
4575+
4576+
let report, mIn, _ = $5
45204577
report (if $1 = "use" then "use!" else "let!") (rhs parseState 1) // report unterminated error
4521-
let spBind = DebugPointAtBinding.Yes(unionRanges (rhs parseState 1) $4.Range)
45224578
let mEquals = rhs parseState 3
45234579
let m = unionRanges (rhs parseState 1) $8.Range
4580+
// Debug point should span from OBINDER through the expression (but not the continuation)
4581+
let spBind = DebugPointAtBinding.Yes(unionRanges (rhs parseState 1) $4.Range)
45244582
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
4525-
SynExpr.LetOrUseBang(spBind, ($1 = "use"), true, $2, $4, $7, $8, m, trivia) }
4526-
4527-
| BINDER headBindingPattern opt_topReturnTypeWithTypeConstraints EQUALS typedSequentialExprBlock IN opt_OBLOCKSEP moreBinders typedSequentialExprBlock %prec expr_let
4528-
{ // Handle type annotations on patterns in let!/use! bindings
4529-
// Examples: let! x: int = async { return 1 }
4530-
// use! _: IDisposable = async { return new MemoryStream() }
4531-
let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 7)
4532-
let pat =
4533-
match $3 with
4534-
| None -> $2
4535-
| Some (_, SynReturnInfo((ty, _), _)) ->
4536-
SynPat.Typed($2, ty, unionRanges $2.Range ty.Range)
4537-
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang pat.Range
4538-
let mEquals = rhs parseState 4
4539-
let m = unionRanges (rhs parseState 1) $9.Range
4540-
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
4541-
SynExpr.LetOrUseBang(spBind, ($1 = "use"), true, pat, $5, $8, $9, m, trivia) }
4542-
4543-
| OBINDER headBindingPattern opt_topReturnTypeWithTypeConstraints EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP moreBinders typedSequentialExprBlock %prec expr_let
4544-
{ // Handle type annotations on patterns in let!/use! bindings (offside-sensitive version)
4545-
// This rule maintains consistent handling of binding constructs across different syntactic contexts
4546-
let report, mIn, _ = $6
4547-
report (if $1 = "use" then "use!" else "let!") (rhs parseState 1) // report unterminated error
4548-
let spBind = DebugPointAtBinding.Yes(unionRanges (rhs parseState 1) $5.Range)
4549-
let pat =
4550-
match $3 with
4551-
| None -> $2
4552-
| Some (_, SynReturnInfo((ty, _), _)) ->
4553-
SynPat.Typed($2, ty, unionRanges $2.Range ty.Range)
4554-
parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AllowTypedLetUseAndBang pat.Range
4555-
let mEquals = rhs parseState 4
4556-
let m = unionRanges (rhs parseState 1) $9.Range
4557-
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
4558-
SynExpr.LetOrUseBang(spBind, ($1 = "use"), true, pat, $5, $8, $9, m, trivia) }
4559-
4560-
| OBINDER headBindingPattern EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP error %prec expr_let
4561-
{ // error recovery that allows intellisense when writing incomplete computation expressions
4583+
4584+
let isUse = ($1 = "use")
4585+
SynExpr.LetOrUseBang(spBind, isUse, true, pat, $4, $7, $8, m, trivia) }
4586+
4587+
| OBINDER ceBindingCore EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP error %prec expr_let
4588+
{ // Error recovery for incomplete let!/use! bindings
4589+
// Allows intellisense to work when writing incomplete computation expressions
4590+
let pat, mPat, isInline, isMutable, tyOpt = $2
4591+
4592+
// Error checking for invalid modifiers
4593+
if isInline then errorR(Error(FSComp.SR.parsInvalidDeclarationSyntax(), rhs parseState 2))
4594+
if isMutable then errorR(Error(FSComp.SR.parsInvalidDeclarationSyntax(), rhs parseState 2))
4595+
4596+
// Debug point should span from OBINDER through the expression
45624597
let spBind = DebugPointAtBinding.Yes(unionRanges (rhs parseState 1) $4.Range)
45634598
let mEquals = rhs parseState 3
45644599
let mAll = unionRanges (rhs parseState 1) (rhs parseState 7)
45654600
let m = $4.Range.EndRange // zero-width range
45664601
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
4567-
SynExpr.LetOrUseBang(spBind, ($1 = "use"), true, $2, $4, [], SynExpr.ImplicitZero m, mAll, trivia) }
4602+
4603+
let isUse = ($1 = "use")
4604+
// Use ImplicitZero as the continuation expression for error recovery
4605+
SynExpr.LetOrUseBang(spBind, isUse, true, pat, $4, [], SynExpr.ImplicitZero m, mAll, trivia) }
45684606

45694607
| DO_BANG typedSequentialExpr IN opt_OBLOCKSEP typedSequentialExprBlock %prec expr_let
45704608
{ let spBind = DebugPointAtBinding.NoneAtDo

tests/FSharp.Compiler.ComponentTests/Language/ComputationExpressionTests.fs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,8 +1576,8 @@ let testParallel3() =
15761576
|> typecheck
15771577
|> shouldFail
15781578
|> withDiagnostics [
1579-
(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.");
1580-
(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.")
1579+
(Error 3350, Line 43, Col 17, 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.");
1580+
(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.")
15811581
]
15821582

15831583
[<Fact>]
@@ -1693,8 +1693,8 @@ let testParallel2() =
16931693
|> typecheck
16941694
|> shouldFail
16951695
|> withDiagnostics [
1696-
(Error 3350, Line 43, Col 48, Line 43, Col 52, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.");
1697-
(Error 3350, Line 42, Col 14, Line 42, Col 48, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.")
1696+
(Error 3350, Line 42, Col 42, Line 42, Col 48, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.");
1697+
(Error 3350, Line 43, Col 48, Line 43, Col 52, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.")
16981698
]
16991699

17001700
[<Fact>]
@@ -1809,10 +1809,10 @@ let testParallel2() =
18091809
|> typecheck
18101810
|> shouldFail
18111811
|> withDiagnostics [
1812-
(Error 3350, Line 35, Col 29, Line 35, Col 45, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.")
1813-
(Error 3350, Line 34, Col 14, Line 34, Col 42, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.")
1812+
(Error 3350, Line 34, Col 29, Line 34, Col 42, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.");
1813+
(Error 3350, Line 35, Col 29, Line 35, Col 45, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.");
1814+
(Error 3350, Line 41, Col 27, Line 41, Col 40, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.");
18141815
(Error 3350, Line 42, Col 27, Line 42, Col 43, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.")
1815-
(Error 3350, Line 41, Col 14, Line 41, Col 40, "Feature 'Allow let! and use! type annotations without requiring parentheses' is not available in F# 9.0. Please use language version 'PREVIEW' or greater.")
18161816
]
18171817

18181818
[<Fact>]

0 commit comments

Comments
 (0)