Skip to content

Commit 398d16d

Browse files
authored
Better ranges for CE let! and use! error reporting. (#17712)
1 parent 767b5ec commit 398d16d

File tree

13 files changed

+250
-42
lines changed

13 files changed

+250
-42
lines changed

docs/release-notes/.FSharp.Compiler.Service/9.0.200.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
### Changed
88

99
* Make ILTypeDef interface impls calculation lazy. ([PR #17392](https://github.com/dotnet/fsharp/pull/17392))
10+
* Better ranges for CE `let!` and `use!` error reporting. ([PR #17712](https://github.com/dotnet/fsharp/pull/17712))
1011

1112
### Breaking Changes

src/Compiler/Checking/Expressions/CheckComputationExpressions.fs

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1589,10 +1589,10 @@ let rec TryTranslateComputationExpression
15891589
Some(TranslateComputationExpression ceenv CompExprTranslationPass.Initial q varSpace innerComp2 translatedCtxt)
15901590

15911591
else
1592-
15931592
if ceenv.isQuery && not (innerComp1.IsArbExprAndThusAlreadyReportedError) then
15941593
match innerComp1 with
1595-
| SynExpr.JoinIn _ -> () // an error will be reported later when we process innerComp1 as a sequential
1594+
| SynExpr.JoinIn _ -> ()
1595+
| SynExpr.DoBang(range = m) -> errorR (Error(FSComp.SR.tcBindMayNotBeUsedInQueries (), m))
15961596
| _ -> errorR (Error(FSComp.SR.tcUnrecognizedQueryOperator (), innerComp1.RangeOfFirstPortion))
15971597

15981598
match
@@ -1854,12 +1854,14 @@ let rec TryTranslateComputationExpression
18541854
// or
18551855
// --> build.BindReturn(e1, (fun _argN -> match _argN with pat -> expr-without-return))
18561856
| SynExpr.LetOrUseBang(
1857-
bindDebugPoint = spBind; isUse = false; isFromSource = isFromSource; pat = pat; rhs = rhsExpr; andBangs = []; body = innerComp) ->
1858-
1859-
let mBind =
1860-
match spBind with
1861-
| DebugPointAtBinding.Yes m -> m
1862-
| _ -> rhsExpr.Range
1857+
bindDebugPoint = spBind
1858+
isUse = false
1859+
isFromSource = isFromSource
1860+
pat = pat
1861+
rhs = rhsExpr
1862+
andBangs = []
1863+
body = innerComp
1864+
trivia = { LetOrUseBangKeyword = mBind }) ->
18631865

18641866
if ceenv.isQuery then
18651867
error (Error(FSComp.SR.tcBindMayNotBeUsedInQueries (), mBind))
@@ -1900,20 +1902,17 @@ let rec TryTranslateComputationExpression
19001902
pat = SynPat.Named(ident = SynIdent(id, _); isThisVal = false) as pat
19011903
rhs = rhsExpr
19021904
andBangs = []
1903-
body = innerComp)
1905+
body = innerComp
1906+
trivia = { LetOrUseBangKeyword = mBind })
19041907
| SynExpr.LetOrUseBang(
19051908
bindDebugPoint = spBind
19061909
isUse = true
19071910
isFromSource = isFromSource
19081911
pat = SynPat.LongIdent(longDotId = SynLongIdent(id = [ id ])) as pat
19091912
rhs = rhsExpr
19101913
andBangs = []
1911-
body = innerComp) ->
1912-
1913-
let mBind =
1914-
match spBind with
1915-
| DebugPointAtBinding.Yes m -> m
1916-
| _ -> rhsExpr.Range
1914+
body = innerComp
1915+
trivia = { LetOrUseBangKeyword = mBind }) ->
19171916

19181917
if ceenv.isQuery then
19191918
error (Error(FSComp.SR.tcBindMayNotBeUsedInQueries (), mBind))
@@ -1988,9 +1987,9 @@ let rec TryTranslateComputationExpression
19881987
Some(translatedCtxt bindExpr)
19891988

19901989
// 'use! pat = e1 ... in e2' where 'pat' is not a simple name -> error
1991-
| SynExpr.LetOrUseBang(isUse = true; pat = pat; andBangs = andBangs) ->
1990+
| SynExpr.LetOrUseBang(isUse = true; andBangs = andBangs; trivia = { LetOrUseBangKeyword = mBind }) ->
19921991
if isNil andBangs then
1993-
error (Error(FSComp.SR.tcInvalidUseBangBinding (), pat.Range))
1992+
error (Error(FSComp.SR.tcInvalidUseBangBinding (), mBind))
19941993
else
19951994
let m =
19961995
match andBangs with
@@ -2013,17 +2012,17 @@ let rec TryTranslateComputationExpression
20132012
rhs = letRhsExpr
20142013
andBangs = andBangBindings
20152014
body = innerComp
2016-
range = letBindRange) ->
2015+
trivia = { LetOrUseBangKeyword = mBind }) ->
20172016
if not (cenv.g.langVersion.SupportsFeature LanguageFeature.AndBang) then
2018-
error (Error(FSComp.SR.tcAndBangNotSupported (), comp.Range))
2017+
let andBangRange =
2018+
match andBangBindings with
2019+
| [] -> comp.Range
2020+
| h :: _ -> h.Trivia.AndBangKeyword
20192021

2020-
if ceenv.isQuery then
2021-
error (Error(FSComp.SR.tcBindMayNotBeUsedInQueries (), letBindRange))
2022+
error (Error(FSComp.SR.tcAndBangNotSupported (), andBangRange))
20222023

2023-
let mBind =
2024-
match spBind with
2025-
| DebugPointAtBinding.Yes m -> m
2026-
| _ -> letRhsExpr.Range
2024+
if ceenv.isQuery then
2025+
error (Error(FSComp.SR.tcBindMayNotBeUsedInQueries (), mBind))
20272026

20282027
let sources =
20292028
(letRhsExpr

src/Compiler/SyntaxTree/SyntaxTrivia.fs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,15 @@ type SynExprLetOrUseTrivia =
9393
[<NoEquality; NoComparison>]
9494
type SynExprLetOrUseBangTrivia =
9595
{
96+
LetOrUseBangKeyword: range
9697
EqualsRange: range option
9798
}
9899

99-
static member Zero: SynExprLetOrUseBangTrivia = { EqualsRange = None }
100+
static member Zero: SynExprLetOrUseBangTrivia =
101+
{
102+
LetOrUseBangKeyword = Range.Zero
103+
EqualsRange = None
104+
}
100105

101106
[<NoEquality; NoComparison>]
102107
type SynExprMatchTrivia =

src/Compiler/SyntaxTree/SyntaxTrivia.fsi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ type SynExprLetOrUseTrivia =
139139
[<NoEquality; NoComparison>]
140140
type SynExprLetOrUseBangTrivia =
141141
{
142+
/// The syntax range of the `let!` or `use!` keyword.
143+
LetOrUseBangKeyword: range
142144
/// The syntax range of the `=` token.
143145
EqualsRange: range option
144146
}

src/Compiler/pars.fsy

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4419,7 +4419,7 @@ declExpr:
44194419
{ let spBind = DebugPointAtBinding.Yes(rhs2 parseState 1 5)
44204420
let mEquals = rhs parseState 3
44214421
let m = unionRanges (rhs parseState 1) $8.Range
4422-
let trivia: SynExprLetOrUseBangTrivia = { EqualsRange = Some mEquals }
4422+
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
44234423
SynExpr.LetOrUseBang(spBind, ($1 = "use"), true, $2, $4, $7, $8, m, trivia) }
44244424

44254425
| OBINDER headBindingPattern EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP moreBinders typedSequentialExprBlock %prec expr_let
@@ -4428,7 +4428,7 @@ declExpr:
44284428
let spBind = DebugPointAtBinding.Yes(unionRanges (rhs parseState 1) $4.Range)
44294429
let mEquals = rhs parseState 3
44304430
let m = unionRanges (rhs parseState 1) $8.Range
4431-
let trivia: SynExprLetOrUseBangTrivia = { EqualsRange = Some mEquals }
4431+
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
44324432
SynExpr.LetOrUseBang(spBind, ($1 = "use"), true, $2, $4, $7, $8, m, trivia) }
44334433

44344434
| OBINDER headBindingPattern EQUALS typedSequentialExprBlock hardwhiteDefnBindingsTerminator opt_OBLOCKSEP error %prec expr_let
@@ -4437,12 +4437,12 @@ declExpr:
44374437
let mEquals = rhs parseState 3
44384438
let mAll = unionRanges (rhs parseState 1) (rhs parseState 7)
44394439
let m = $4.Range.EndRange // zero-width range
4440-
let trivia: SynExprLetOrUseBangTrivia = { EqualsRange = Some mEquals }
4440+
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = rhs parseState 1 ; EqualsRange = Some mEquals }
44414441
SynExpr.LetOrUseBang(spBind, ($1 = "use"), true, $2, $4, [], SynExpr.ImplicitZero m, mAll, trivia) }
44424442

44434443
| DO_BANG typedSequentialExpr IN opt_OBLOCKSEP typedSequentialExprBlock %prec expr_let
44444444
{ let spBind = DebugPointAtBinding.NoneAtDo
4445-
let trivia: SynExprLetOrUseBangTrivia = { EqualsRange = None }
4445+
let trivia: SynExprLetOrUseBangTrivia = { LetOrUseBangKeyword = Range.Zero; EqualsRange = None }
44464446
SynExpr.LetOrUseBang(spBind, false, true, SynPat.Const(SynConst.Unit, $2.Range), $2, [], $5, unionRanges (rhs parseState 1) $5.Range, trivia) }
44474447

44484448
| ODO_BANG typedSequentialExprBlock hardwhiteDefnBindingsTerminator %prec expr_let

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

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,200 @@ let run r2 r3 =
246246
|> shouldFail
247247
|> withDiagnostics [
248248
(Error 3345, Line 22, Col 9, Line 22, Col 13, "use! may not be combined with and!")
249+
]
250+
251+
[<Fact>]
252+
let ``This control construct may only be used if the computation expression builder defines a 'Bind' method`` () =
253+
Fsx """
254+
module Result =
255+
let zip x1 x2 =
256+
match x1,x2 with
257+
| Ok x1res, Ok x2res -> Ok (x1res, x2res)
258+
| Error e, _ -> Error e
259+
| _, Error e -> Error e
260+
261+
type ResultBuilder() =
262+
member _.MergeSources(t1: Result<'T,'U>, t2: Result<'T1,'U>) = Result.zip t1 t2
263+
member _.BindReturn(x: Result<'T,'U>, f) = Result.map f x
264+
member _.Delay(f) = f()
265+
266+
member _.TryWith(r: Result<'T,'U>, f) =
267+
match r with
268+
| Ok x -> Ok x
269+
| Error e -> f e
270+
271+
let result = ResultBuilder()
272+
273+
let run r2 r3 =
274+
result {
275+
let! a = r2
276+
return! a
277+
}
278+
"""
279+
|> ignoreWarnings
280+
|> typecheck
281+
|> shouldFail
282+
|> withDiagnostics [
283+
(Error 708, Line 23, Col 9, Line 23, Col 13, "This control construct may only be used if the computation expression builder defines a 'Bind' method")
284+
]
285+
286+
[<Fact>]
287+
let ``This control construct may only be used if the computation expression builder defines a 'Using' method`` () =
288+
Fsx """
289+
module Result =
290+
let zip x1 x2 =
291+
match x1,x2 with
292+
| Ok x1res, Ok x2res -> Ok (x1res, x2res)
293+
| Error e, _ -> Error e
294+
| _, Error e -> Error e
295+
296+
type ResultBuilder() =
297+
member _.MergeSources(t1: Result<'T,'U>, t2: Result<'T1,'U>) = Result.zip t1 t2
298+
member _.BindReturn(x: Result<'T,'U>, f) = Result.map f x
299+
member _.Delay(f) = f()
300+
301+
member _.TryWith(r: Result<'T,'U>, f) =
302+
match r with
303+
| Ok x -> Ok x
304+
| Error e -> f e
305+
306+
let result = ResultBuilder()
307+
308+
let run r2 r3 =
309+
result {
310+
use! a = r2
311+
return! a
312+
}
313+
"""
314+
|> ignoreWarnings
315+
|> typecheck
316+
|> shouldFail
317+
|> withDiagnostics [
318+
(Error 708, Line 23, Col 9, Line 23, Col 13, "This control construct may only be used if the computation expression builder defines a 'Using' method")
319+
]
320+
321+
[<Fact>]
322+
let ``do! expressions may not be used in queries`` () =
323+
Fsx """
324+
query {
325+
do! failwith ""
326+
yield 1
327+
}
328+
"""
329+
|> ignoreWarnings
330+
|> typecheck
331+
|> shouldFail
332+
|> withDiagnostics [
333+
(Error 3143, Line 3, Col 5, Line 3, Col 20, "'let!', 'use!' and 'do!' expressions may not be used in queries")
334+
]
335+
336+
[<Fact>]
337+
let ``let! expressions may not be used in queries`` () =
338+
Fsx """
339+
query {
340+
let! x = failwith ""
341+
yield 1
342+
}
343+
"""
344+
|> ignoreWarnings
345+
|> typecheck
346+
|> shouldFail
347+
|> withDiagnostics [
348+
(Error 3143, Line 3, Col 5, Line 3, Col 9, "'let!', 'use!' and 'do!' expressions may not be used in queries")
349+
]
350+
351+
[<Fact>]
352+
let ``let!, and! expressions may not be used in queries`` () =
353+
Fsx """
354+
query {
355+
let! x = failwith ""
356+
and! y = failwith ""
357+
yield 1
358+
}
359+
"""
360+
|> ignoreWarnings
361+
|> typecheck
362+
|> shouldFail
363+
|> withDiagnostics [
364+
(Error 3143, Line 3, Col 5, Line 3, Col 9, "'let!', 'use!' and 'do!' expressions may not be used in queries")
365+
]
366+
367+
[<Fact>]
368+
let ``use! expressions may not be used in queries`` () =
369+
Fsx """
370+
query {
371+
use! x = failwith ""
372+
yield 1
373+
}
374+
"""
375+
|> ignoreWarnings
376+
|> typecheck
377+
|> shouldFail
378+
|> withDiagnostics [
379+
(Error 3143, Line 3, Col 5, Line 3, Col 9, "'let!', 'use!' and 'do!' expressions may not be used in queries")
380+
]
381+
382+
[<Fact>]
383+
let ``do! expressions may not be used in queries(SynExpr.Sequential)`` () =
384+
Fsx """
385+
query {
386+
for c in [1..10] do
387+
do! failwith ""
388+
yield 1
389+
}
390+
"""
391+
|> ignoreWarnings
392+
|> typecheck
393+
|> shouldFail
394+
|> withDiagnostics [
395+
(Error 3143, Line 4, Col 5, Line 4, Col 20, "'let!', 'use!' and 'do!' expressions may not be used in queries")
396+
]
397+
398+
[<Fact>]
399+
let ``let! expressions may not be used in queries(SynExpr.Sequential)`` () =
400+
Fsx """
401+
query {
402+
for c in [1..10] do
403+
let! x = failwith ""
404+
yield 1
405+
}
406+
"""
407+
|> ignoreWarnings
408+
|> typecheck
409+
|> shouldFail
410+
|> withDiagnostics [
411+
(Error 3143, Line 4, Col 5, Line 4, Col 9, "'let!', 'use!' and 'do!' expressions may not be used in queries")
412+
]
413+
414+
[<Fact>]
415+
let ``let!, and! expressions may not be used in queries(SynExpr.Sequential)`` () =
416+
Fsx """
417+
query {
418+
for c in [1..10] do
419+
let! x = failwith ""
420+
and! y = failwith ""
421+
yield 1
422+
}
423+
"""
424+
|> ignoreWarnings
425+
|> typecheck
426+
|> shouldFail
427+
|> withDiagnostics [
428+
(Error 3143, Line 4, Col 5, Line 4, Col 9, "'let!', 'use!' and 'do!' expressions may not be used in queries")
429+
]
430+
431+
[<Fact>]
432+
let ``use! expressions may not be used in queries(SynExpr.Sequential)`` () =
433+
Fsx """
434+
query {
435+
for c in [1..10] do
436+
use! x = failwith ""
437+
yield 1
438+
}
439+
"""
440+
|> ignoreWarnings
441+
|> typecheck
442+
|> shouldFail
443+
|> withDiagnostics [
444+
(Error 3143, Line 4, Col 5, Line 4, Col 9, "'let!', 'use!' and 'do!' expressions may not be used in queries")
249445
]

tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10203,7 +10203,9 @@ FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: FSharp.Compiler.SyntaxTr
1020310203
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] EqualsRange
1020410204
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] get_EqualsRange()
1020510205
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: System.String ToString()
10206-
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: Void .ctor(Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range])
10206+
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: FSharp.Compiler.Text.Range LetOrUseBangKeyword
10207+
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: FSharp.Compiler.Text.Range get_LetOrUseBangKeyword()
10208+
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: Void .ctor(FSharp.Compiler.Text.Range, Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range])
1020710209
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseTrivia: FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseTrivia Zero
1020810210
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseTrivia: FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseTrivia get_Zero()
1020910211
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] InKeyword

tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10203,7 +10203,9 @@ FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: FSharp.Compiler.SyntaxTr
1020310203
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] EqualsRange
1020410204
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] get_EqualsRange()
1020510205
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: System.String ToString()
10206-
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: Void .ctor(Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range])
10206+
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: FSharp.Compiler.Text.Range LetOrUseBangKeyword
10207+
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: FSharp.Compiler.Text.Range get_LetOrUseBangKeyword()
10208+
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseBangTrivia: Void .ctor(FSharp.Compiler.Text.Range, Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range])
1020710209
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseTrivia: FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseTrivia Zero
1020810210
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseTrivia: FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseTrivia get_Zero()
1020910211
FSharp.Compiler.SyntaxTrivia.SynExprLetOrUseTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] InKeyword

0 commit comments

Comments
 (0)