diff --git a/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md index 2f370f86aef..e4d4ed4b96b 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md @@ -43,6 +43,7 @@ * Applied nullable reference types to FSharp.Compiler.Service itself ([PR #15310](https://github.com/dotnet/fsharp/pull/15310)) * Ensure that isinteractive multi-emit backing fields are not public. ([Issue #17439](https://github.com/dotnet/fsharp/issues/17438)), ([PR #17439](https://github.com/dotnet/fsharp/pull/17439)) * Better error reporting for unions with duplicated fields. ([PR #17521](https://github.com/dotnet/fsharp/pull/17521)) +* Better CE error reporting when using `use!` with `and!` ([PR #17671](https://github.com/dotnet/fsharp/pull/17671)) * Better error reporting for let bindings. ([PR #17601](https://github.com/dotnet/fsharp/pull/17601)) * Optimize ILTypeDef interface impls reading from metadata. ([PR #17382](https://github.com/dotnet/fsharp/pull/17382)) * Better error reporting for active patterns. ([PR #17666](https://github.com/dotnet/fsharp/pull/17666)) diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index a5c533d8bfd..3a3682fbf53 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -1987,12 +1987,17 @@ let rec TryTranslateComputationExpression Some(translatedCtxt bindExpr) - // 'use! pat = e1 ... in e2' where 'pat' is not a simple name --> error + // 'use! pat = e1 ... in e2' where 'pat' is not a simple name -> error | SynExpr.LetOrUseBang(isUse = true; pat = pat; andBangs = andBangs) -> if isNil andBangs then error (Error(FSComp.SR.tcInvalidUseBangBinding (), pat.Range)) else - error (Error(FSComp.SR.tcInvalidUseBangBindingNoAndBangs (), comp.Range)) + let m = + match andBangs with + | [] -> comp.Range + | h :: _ -> h.Trivia.AndBangKeyword + + error (Error(FSComp.SR.tcInvalidUseBangBindingNoAndBangs (), m)) // 'let! pat1 = expr1 and! pat2 = expr2 in ...' --> // build.BindN(expr1, expr2, ...) diff --git a/src/Compiler/SyntaxTree/SyntaxTree.fs b/src/Compiler/SyntaxTree/SyntaxTree.fs index 5a9b158875d..50c81fdcc58 100644 --- a/src/Compiler/SyntaxTree/SyntaxTree.fs +++ b/src/Compiler/SyntaxTree/SyntaxTree.fs @@ -870,6 +870,14 @@ type SynExprAndBang = range: range * trivia: SynExprAndBangTrivia + member x.Range = + match x with + | SynExprAndBang(range = range) -> range + + member this.Trivia = + match this with + | SynExprAndBang(trivia = trivia) -> trivia + [] type SynExprRecordField = | SynExprRecordField of diff --git a/src/Compiler/SyntaxTree/SyntaxTree.fsi b/src/Compiler/SyntaxTree/SyntaxTree.fsi index 9c7b83083df..2cc6b14947a 100644 --- a/src/Compiler/SyntaxTree/SyntaxTree.fsi +++ b/src/Compiler/SyntaxTree/SyntaxTree.fsi @@ -982,6 +982,12 @@ type SynExprAndBang = range: range * trivia: SynExprAndBangTrivia + /// Gets the syntax range of this construct + member Range: range + + /// Gets the trivia associated with this construct + member Trivia: SynExprAndBangTrivia + [] type SynExprRecordField = | SynExprRecordField of diff --git a/src/Compiler/SyntaxTree/SyntaxTrivia.fs b/src/Compiler/SyntaxTree/SyntaxTrivia.fs index 82c581520b4..f259746cfff 100644 --- a/src/Compiler/SyntaxTree/SyntaxTrivia.fs +++ b/src/Compiler/SyntaxTree/SyntaxTrivia.fs @@ -270,6 +270,7 @@ type SynBindingTrivia = [] type SynExprAndBangTrivia = { + AndBangKeyword: range EqualsRange: range InKeyword: range option } diff --git a/src/Compiler/SyntaxTree/SyntaxTrivia.fsi b/src/Compiler/SyntaxTree/SyntaxTrivia.fsi index 79c7903e78c..567586081f2 100644 --- a/src/Compiler/SyntaxTree/SyntaxTrivia.fsi +++ b/src/Compiler/SyntaxTree/SyntaxTrivia.fsi @@ -334,9 +334,10 @@ type SynBindingTrivia = [] type SynExprAndBangTrivia = { + /// The syntax range of the `and!` keyword + AndBangKeyword: range /// The syntax range of the `=` token. EqualsRange: range - /// The syntax range of the `in` keyword. InKeyword: range option } diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy index e355a31e2f3..06c108e9b4d 100644 --- a/src/Compiler/pars.fsy +++ b/src/Compiler/pars.fsy @@ -4078,7 +4078,8 @@ moreBinders: let mEquals = rhs parseState 3 let m = unionRanges (rhs parseState 1) $4.Range let mIn = rhs parseState 5 - SynExprAndBang(spBind, $1, true, $2, $4, m, { EqualsRange = mEquals; InKeyword = Some mIn }) :: $6 } + let trivia = { AndBangKeyword = rhs parseState 1; EqualsRange = mEquals; InKeyword = Some mIn } + SynExprAndBang(spBind, $1, true, $2, $4, m, trivia) :: $6 } | OAND_BANG headBindingPattern EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP moreBinders %prec expr_let { let report, mIn, _ = $5 @@ -4086,7 +4087,8 @@ moreBinders: let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 5) (* TODO Pretty sure this is wrong *) let mEquals = rhs parseState 3 let m = unionRanges (rhs parseState 1) $4.Range - SynExprAndBang(spBind, $1, true, $2, $4, m, { EqualsRange = mEquals; InKeyword = mIn }) :: $7 } + let trivia = { AndBangKeyword = rhs parseState 1; EqualsRange = mEquals; InKeyword = mIn } + SynExprAndBang(spBind, $1, true, $2, $4, m, trivia) :: $7 } | %prec prec_no_more_attr_bindings { [] } diff --git a/tests/FSharp.Compiler.ComponentTests/Language/ComputationExpressionTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/ComputationExpressionTests.fs index af588b04975..d5c6a5a0a63 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/ComputationExpressionTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/ComputationExpressionTests.fs @@ -152,3 +152,98 @@ let x = A.Prop { return 0 } |> compile |> shouldSucceed |> ignore + + [] + let ``use! may not be combined with and!`` () = + Fsx """ +module Result = + let zip x1 x2 = + match x1,x2 with + | Ok x1res, Ok x2res -> Ok (x1res, x2res) + | Error e, _ -> Error e + | _, Error e -> Error e + +type ResultBuilder() = + member _.MergeSources(t1: Result<'T,'U>, t2: Result<'T1,'U>) = Result.zip t1 t2 + member _.BindReturn(x: Result<'T,'U>, f) = Result.map f x + +let result = ResultBuilder() + +let run r2 r3 = + result { + use! b = r2 + and! c = r3 + return b - c + } + """ + |> ignoreWarnings + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 3345, Line 18, Col 9, Line 18, Col 13, "use! may not be combined with and!") + ] + + [] + let ``multiple use! may not be combined with and!`` () = + Fsx """ +module Result = + let zip x1 x2 = + match x1,x2 with + | Ok x1res, Ok x2res -> Ok (x1res, x2res) + | Error e, _ -> Error e + | _, Error e -> Error e + +type ResultBuilder() = + member _.MergeSources(t1: Result<'T,'U>, t2: Result<'T1,'U>) = Result.zip t1 t2 + member _.BindReturn(x: Result<'T,'U>, f) = Result.map f x + +let result = ResultBuilder() + +let run r2 r3 = + result { + use! b = r2 + and! c = r3 + use! d = r2 + return b - c + } + """ + |> ignoreWarnings + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 3345, Line 18, Col 9, Line 18, Col 13, "use! may not be combined with and!") + ] + + [] + let ``multiple use! may not be combined with multiple and!`` () = + Fsx """ +module Result = + let zip x1 x2 = + match x1,x2 with + | Ok x1res, Ok x2res -> Ok (x1res, x2res) + | Error e, _ -> Error e + | _, Error e -> Error e + +type ResultBuilder() = + member _.MergeSources(t1: Result<'T,'U>, t2: Result<'T1,'U>) = Result.zip t1 t2 + member _.BindReturn(x: Result<'T,'U>, f) = Result.map f x + member _.BindReturn(x: Result<'T,'U>, f) = Result.map f x + member _.Bind(x: Result<'T,'U>, f) = Result.bind f x + +let result = ResultBuilder() + +let run r2 r3 = + result { + let! c = r3 + and! c = r3 + use! b = r2 + and! c = r3 + return b - c + } + """ + |> ignoreWarnings + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 3345, Line 22, Col 9, Line 22, Col 13, "use! may not be combined with and!") + ] \ No newline at end of file diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl index 58e660c6ae1..03b461d6a54 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl @@ -7740,8 +7740,12 @@ FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Syntax.SynExpr get_body() FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Syntax.SynExprAndBang NewSynExprAndBang(FSharp.Compiler.Syntax.DebugPointAtBinding, Boolean, Boolean, FSharp.Compiler.Syntax.SynPat, FSharp.Compiler.Syntax.SynExpr, FSharp.Compiler.Text.Range, FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia) FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Syntax.SynPat get_pat() FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Syntax.SynPat pat +FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia Trivia +FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia get_Trivia() FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia get_trivia() FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia trivia +FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Text.Range Range +FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Text.Range get_Range() FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Text.Range get_range() FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Text.Range range FSharp.Compiler.Syntax.SynExprAndBang: Int32 Tag @@ -10139,12 +10143,14 @@ FSharp.Compiler.SyntaxTrivia.SynEnumCaseTrivia: Microsoft.FSharp.Core.FSharpOpti FSharp.Compiler.SyntaxTrivia.SynEnumCaseTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] get_BarRange() FSharp.Compiler.SyntaxTrivia.SynEnumCaseTrivia: System.String ToString() FSharp.Compiler.SyntaxTrivia.SynEnumCaseTrivia: Void .ctor(Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range], FSharp.Compiler.Text.Range) +FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: FSharp.Compiler.Text.Range AndBangKeyword FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: FSharp.Compiler.Text.Range EqualsRange +FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: FSharp.Compiler.Text.Range get_AndBangKeyword() FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: FSharp.Compiler.Text.Range get_EqualsRange() FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] InKeyword FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] get_InKeyword() FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: System.String ToString() -FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: Void .ctor(FSharp.Compiler.Text.Range, Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range]) +FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: Void .ctor(FSharp.Compiler.Text.Range, FSharp.Compiler.Text.Range, Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range]) FSharp.Compiler.SyntaxTrivia.SynExprAnonRecdTrivia: FSharp.Compiler.Text.Range OpeningBraceRange FSharp.Compiler.SyntaxTrivia.SynExprAnonRecdTrivia: FSharp.Compiler.Text.Range get_OpeningBraceRange() FSharp.Compiler.SyntaxTrivia.SynExprAnonRecdTrivia: System.String ToString() diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl index 58e660c6ae1..03b461d6a54 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl @@ -7740,8 +7740,12 @@ FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Syntax.SynExpr get_body() FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Syntax.SynExprAndBang NewSynExprAndBang(FSharp.Compiler.Syntax.DebugPointAtBinding, Boolean, Boolean, FSharp.Compiler.Syntax.SynPat, FSharp.Compiler.Syntax.SynExpr, FSharp.Compiler.Text.Range, FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia) FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Syntax.SynPat get_pat() FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Syntax.SynPat pat +FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia Trivia +FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia get_Trivia() FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia get_trivia() FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia trivia +FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Text.Range Range +FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Text.Range get_Range() FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Text.Range get_range() FSharp.Compiler.Syntax.SynExprAndBang: FSharp.Compiler.Text.Range range FSharp.Compiler.Syntax.SynExprAndBang: Int32 Tag @@ -10139,12 +10143,14 @@ FSharp.Compiler.SyntaxTrivia.SynEnumCaseTrivia: Microsoft.FSharp.Core.FSharpOpti FSharp.Compiler.SyntaxTrivia.SynEnumCaseTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] get_BarRange() FSharp.Compiler.SyntaxTrivia.SynEnumCaseTrivia: System.String ToString() FSharp.Compiler.SyntaxTrivia.SynEnumCaseTrivia: Void .ctor(Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range], FSharp.Compiler.Text.Range) +FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: FSharp.Compiler.Text.Range AndBangKeyword FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: FSharp.Compiler.Text.Range EqualsRange +FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: FSharp.Compiler.Text.Range get_AndBangKeyword() FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: FSharp.Compiler.Text.Range get_EqualsRange() FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] InKeyword FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] get_InKeyword() FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: System.String ToString() -FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: Void .ctor(FSharp.Compiler.Text.Range, Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range]) +FSharp.Compiler.SyntaxTrivia.SynExprAndBangTrivia: Void .ctor(FSharp.Compiler.Text.Range, FSharp.Compiler.Text.Range, Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range]) FSharp.Compiler.SyntaxTrivia.SynExprAnonRecdTrivia: FSharp.Compiler.Text.Range OpeningBraceRange FSharp.Compiler.SyntaxTrivia.SynExprAnonRecdTrivia: FSharp.Compiler.Text.Range get_OpeningBraceRange() FSharp.Compiler.SyntaxTrivia.SynExprAnonRecdTrivia: System.String ToString() diff --git a/tests/service/data/SyntaxTree/ComputationExpression/MultipleSynExprAndBangHaveRangeThatStartsAtAndAndEndsAfterExpression.fs.bsl b/tests/service/data/SyntaxTree/ComputationExpression/MultipleSynExprAndBangHaveRangeThatStartsAtAndAndEndsAfterExpression.fs.bsl index a5a22fcb93f..2c7ca108bc8 100644 --- a/tests/service/data/SyntaxTree/ComputationExpression/MultipleSynExprAndBangHaveRangeThatStartsAtAndAndEndsAfterExpression.fs.bsl +++ b/tests/service/data/SyntaxTree/ComputationExpression/MultipleSynExprAndBangHaveRangeThatStartsAtAndAndEndsAfterExpression.fs.bsl @@ -26,7 +26,8 @@ ImplFile App (NonAtomic, false, Ident getFoo, Const (Unit, (4,22--4,24)), (4,15--4,24)), - (4,4--4,24), { EqualsRange = (4,13--4,14) + (4,4--4,24), { AndBangKeyword = (4,4--4,8) + EqualsRange = (4,13--4,14) InKeyword = Some (4,25--4,27) }); SynExprAndBang (Yes (5,4--6,10), false, true, @@ -35,7 +36,8 @@ ImplFile App (NonAtomic, false, Ident getMeh, Const (Unit, (5,22--5,24)), (5,15--5,24)), - (5,4--5,24), { EqualsRange = (5,13--5,14) + (5,4--5,24), { AndBangKeyword = (5,4--5,8) + EqualsRange = (5,13--5,14) InKeyword = None })], YieldOrReturn ((false, true), Ident bar, (6,4--6,14)), (3,4--6,14), { EqualsRange = Some (3,13--3,14) }), diff --git a/tests/service/data/SyntaxTree/ComputationExpression/SynExprAndBangRangeStartsAtAndAndEndsAfterExpression.fs.bsl b/tests/service/data/SyntaxTree/ComputationExpression/SynExprAndBangRangeStartsAtAndAndEndsAfterExpression.fs.bsl index 837d642b442..105a94f5b46 100644 --- a/tests/service/data/SyntaxTree/ComputationExpression/SynExprAndBangRangeStartsAtAndAndEndsAfterExpression.fs.bsl +++ b/tests/service/data/SyntaxTree/ComputationExpression/SynExprAndBangRangeStartsAtAndAndEndsAfterExpression.fs.bsl @@ -25,7 +25,8 @@ ImplFile App (NonAtomic, false, Ident getFoo, Const (Unit, (5,22--5,24)), (5,15--5,24)), - (5,4--5,24), { EqualsRange = (5,13--5,14) + (5,4--5,24), { AndBangKeyword = (5,4--5,8) + EqualsRange = (5,13--5,14) InKeyword = None })], YieldOrReturn ((false, true), Ident bar, (7,4--7,14)), (3,4--7,14), { EqualsRange = Some (3,13--3,14) }), diff --git a/tests/service/data/SyntaxTree/Expression/SynExprLetOrUseBangContainsTheRangeOfTheEqualsSign.fs.bsl b/tests/service/data/SyntaxTree/Expression/SynExprLetOrUseBangContainsTheRangeOfTheEqualsSign.fs.bsl index 6f2b839ce00..7dfd05fc1ad 100644 --- a/tests/service/data/SyntaxTree/Expression/SynExprLetOrUseBangContainsTheRangeOfTheEqualsSign.fs.bsl +++ b/tests/service/data/SyntaxTree/Expression/SynExprLetOrUseBangContainsTheRangeOfTheEqualsSign.fs.bsl @@ -22,7 +22,8 @@ ImplFile App (NonAtomic, false, Ident someFunction, Const (Unit, (4,26--4,28)), (4,13--4,28)), - (4,4--4,28), { EqualsRange = (4,11--4,12) + (4,4--4,28), { AndBangKeyword = (4,4--4,8) + EqualsRange = (4,11--4,12) InKeyword = None })], YieldOrReturn ((false, true), Const (Unit, (5,11--5,13)), (5,4--5,13)),