diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 67267ff2581..aaccaaba9ea 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -1,12 +1,12 @@ ### Fixed +* Preserve source range for type errors on empty-bodied computation expressions (e.g. `foo {}`) in pipelines, function arguments, and type-annotated contexts, instead of reporting `unknown(1,1)`. ([Issue #19550](https://github.com/dotnet/fsharp/issues/19550), [PR #19849](https://github.com/dotnet/fsharp/pull/19849)) * Tooltip "Full name" now shows demangled companion module names (e.g. `MyType.func` instead of `MyTypeModule.func`). ([Issue #17335](https://github.com/dotnet/fsharp/issues/17335), [PR #19867](https://github.com/dotnet/fsharp/pull/19867)) * Fix internal error (FS0193) when calling an indexed property setter with a named argument that matches an indexer parameter. ([Issue #16034](https://github.com/dotnet/fsharp/issues/16034), [PR #19851](https://github.com/dotnet/fsharp/pull/19851)) * Fix missing FS1182 ("unused binding") warning for unused `let` function bindings inside class types. ([Issue #13849](https://github.com/dotnet/fsharp/issues/13849), [PR #19805](https://github.com/dotnet/fsharp/pull/19805)) * Fix inner mutually-recursive `let rec ... and ...` functions under `--realsig+` not being lifted to top-level static methods (TLR), causing `FSharpFunc` closure allocations and loss of `tail.` opcodes — the large struct-mutual-recursion perf regression reported in [Issue #17607](https://github.com/dotnet/fsharp/issues/17607). ([PR #19882](https://github.com/dotnet/fsharp/pull/19882)) * Fix `TypeLoadException` ("Specialize tried to implicitly override a method with weaker type parameter constraints") and the related CLR crash with constrained inline calls by stripping constraints from closure-class typars in `EraseClosures.convIlxClosureDef`. ([Issue #14492](https://github.com/dotnet/fsharp/issues/14492), [Issue #19075](https://github.com/dotnet/fsharp/issues/19075), [PR #19882](https://github.com/dotnet/fsharp/pull/19882)) * Fix `FieldAccessException` at runtime when the optimizer relocates a read of a `protected` (family) base-class field into a method outside the field's family (e.g. a trivial member inlined into module/startup code under `--optimize+`). Protected (family) IL field access is no longer hoisted out of its declaring family by inlining or method-splitting. ([Issue #19963](https://github.com/dotnet/fsharp/issues/19963), [PR #19964](https://github.com/dotnet/fsharp/pull/19964)) - * Suppress hover/symbol resolution for wildcard `_` patterns inside `member _.…` bodies that incorrectly showed `val _: T` tooltip. ([PR #19760](https://github.com/dotnet/fsharp/pull/19760)) * Deduplicate format specifier locations in computation expressions so editor tooling no longer reports duplicate entries for the same `%` specifier. ([Issue #16419](https://github.com/dotnet/fsharp/issues/16419), [PR #19791](https://github.com/dotnet/fsharp/pull/19791)) * Reject non-function bindings for single-case and partial active pattern names with FS1209, matching the existing multi-case behavior. ([PR #19763](https://github.com/dotnet/fsharp/pull/19763)) diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index b05df442d36..2e258b1b2c9 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -1610,7 +1610,8 @@ let rec TryTranslateComputationExpression error (Error(FSComp.SR.tcEmptyBodyRequiresBuilderZeroMethod (), ceenv.mWhole)) | _ -> error (Error(FSComp.SR.tcRequireBuilderMethod "Zero", m)) - Some(translatedCtxt (mkSynCall "Zero" m [] ceenv.builderValName)) + let mCall = if equals m range0 then ceenv.mWhole else m + Some(translatedCtxt (mkSynCall "Zero" mCall [] ceenv.builderValName)) | OptionalSequential(JoinOrGroupJoinOrZipClause ceenv (_, _, _, _, _, mClause), _) when firstTry = CompExprTranslationPass.Initial -> diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 070ce6b3e8e..dfd00c69880 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -8884,7 +8884,7 @@ and TcApplicationThen (cenv: cenv) (overallTy: OverallTy) env tpenv mExprAndArg // leftExpr { } | SynExpr.ComputationExpr (false, comp, _m) | SynExpr.Record (None, None, EmptyFieldListAsUnit comp, _m) -> - let bodyOfCompExpr, tpenv = cenv.TcComputationExpression cenv env overallTy tpenv (mLeftExpr, leftExpr.Expr, exprTy, comp) + let bodyOfCompExpr, tpenv = cenv.TcComputationExpression cenv env overallTy tpenv (mExprAndArg, leftExpr.Expr, exprTy, comp) TcDelayed cenv overallTy env tpenv mExprAndArg (MakeApplicableExprNoFlex cenv bodyOfCompExpr) (tyOfExpr g bodyOfCompExpr) ExprAtomicFlag.NonAtomic delayed | _ -> diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 3fc031a525f..c109944620f 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -355,6 +355,7 @@ + diff --git a/tests/FSharp.Compiler.ComponentTests/Language/CE_PipelineRange19550.fs b/tests/FSharp.Compiler.ComponentTests/Language/CE_PipelineRange19550.fs new file mode 100644 index 00000000000..619fcfa179a --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Language/CE_PipelineRange19550.fs @@ -0,0 +1,278 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Language + +open Xunit +open FSharp.Test.Compiler + +// https://github.com/dotnet/fsharp/issues/19550 +module CE_PipelineRange19550 = + + let private errorsOf (result: CompilationResult) : ErrorInfo list = + let diags = + match result with + | CompilationResult.Success r + | CompilationResult.Failure r -> r.Diagnostics + diags + |> List.filter (fun d -> + match d.Error with ErrorType.Error _ -> true | _ -> false) + + let private dump (result: CompilationResult) : string = + errorsOf result + |> List.map (fun d -> + let n = + match d.Error with + | ErrorType.Error n + | ErrorType.Warning n + | ErrorType.Information n + | ErrorType.Hidden n -> n + sprintf " FS%04d (L%d,C%d)-(L%d,C%d): %s" + n d.Range.StartLine (d.Range.StartColumn + 1) + d.Range.EndLine (d.Range.EndColumn + 1) d.Message) + |> String.concat "\n" + + let private hasDiagAt (code: int) (sLine, sCol, eLine, eCol) (result: CompilationResult) : CompilationResult = + let found = + errorsOf result + |> List.exists (fun d -> + (match d.Error with ErrorType.Error c -> c = code | _ -> false) + && d.Range.StartLine = sLine + && d.Range.StartColumn + 1 = sCol + && d.Range.EndLine = eLine + && d.Range.EndColumn + 1 = eCol) + if not found then + failwithf + "Expected diagnostic FS%04d at (Line %d, Col %d)-(Line %d, Col %d). Actual diagnostics:\n%s" + code sLine sCol eLine eCol (dump result) + result + + let private hasNoRange0Error (result: CompilationResult) : CompilationResult = + let bad = + errorsOf result + |> List.filter (fun d -> + d.Range.StartLine = 1 && d.Range.StartColumn = 0 + && d.Range.EndLine = 1 && d.Range.EndColumn = 0) + if not (List.isEmpty bad) then + failwithf + "Unexpected range0 / unknown(1,1) error diagnostic. All diagnostics:\n%s" + (dump result) + result + + [] + let ``Issue 19550 - empty CE body in pipeline reports source range``() = + FSharp """ +module Repro19550_01 +type FooBuilder() = + member _.Zero() = fun x -> x + 42 + +let foo = FooBuilder() + +"" |> foo {} |> printfn "%d" + """ + |> compile + |> shouldFail + |> hasDiagAt 193 (8, 7, 8, 13) + + [] + let ``Issue 19550 - single pipe with empty CE body has non-zero range``() = + FSharp """ +module Repro19550_02 +type FooBuilder() = + member _.Zero() = fun x -> x + 42 + +let foo = FooBuilder() + +foo {} |> printfn "%d" + """ + |> compile + |> shouldFail + |> hasNoRange0Error + + [] + let ``Issue 19550 - empty CE body as function argument``() = + FSharp """ +module Repro19550_03 +type FooBuilder() = + member _.Zero() = fun x -> x + 42 + +let foo = FooBuilder() + +let take (x: int) = x +let _ = take (foo {}) + """ + |> compile + |> shouldFail + |> hasDiagAt 193 (9, 15, 9, 21) + + [] + let ``Issue 19550 - explicit type annotation mismatch on empty CE body``() = + FSharp """ +module Repro19550_04 +type FooBuilder() = + member _.Zero() = fun x -> x + 42 + +let foo = FooBuilder() + +let x : int = foo {} + """ + |> compile + |> shouldFail + |> hasDiagAt 193 (8, 15, 8, 21) + + [] + let ``Issue 19550 - Yield builder in pipeline keeps non-zero range``() = + FSharp """ +module Repro19550_05a +type YBuilder() = + member _.Yield(x) = fun y -> x + y + +let yb = YBuilder() + +"" |> yb { yield 1 } |> printfn "%d" + """ + |> compile + |> shouldFail + |> hasNoRange0Error + + [] + let ``Issue 19550 - Return builder in pipeline keeps non-zero range``() = + FSharp """ +module Repro19550_05b +type RBuilder() = + member _.Return(x) = fun y -> x + y + +let rb = RBuilder() + +"" |> rb { return 1 } |> printfn "%d" + """ + |> compile + |> shouldFail + |> hasNoRange0Error + + [] + let ``Issue 19550 - Bind builder in pipeline keeps non-zero range``() = + FSharp """ +module Repro19550_05c +type BBuilder() = + member _.Bind(x, f) = f x + member _.Return(x) = fun y -> x + y + +let bb = BBuilder() + +"" |> bb { let! x = 1 in return x } |> printfn "%d" + """ + |> compile + |> shouldFail + |> hasNoRange0Error + + [] + let ``Issue 19550 - nested CE - inner empty body in pipeline``() = + FSharp """ +module Repro19550_06 +type FooBuilder() = + member _.Zero() = fun x -> x + 42 + +let foo = FooBuilder() + +type Outer() = + member _.Yield(_) : unit = () + member _.Combine(_, _) = () + member _.Delay(f: unit -> unit) = f () + member _.Zero() = () + +let outer = Outer() + +let _ = outer { do "" |> foo {} |> ignore } + """ + |> compile + |> shouldFail + |> hasDiagAt 193 (16, 26, 16, 32) + + [] + let ``Issue 19550 - valid empty CE body without type mismatch still compiles``() = + FSharp """ +module Repro19550_07 +type IdBuilder() = + member _.Zero() = 0 + +let id1 = IdBuilder() +let v : int = id1 {} + """ + |> compile + |> shouldSucceed + |> ignore + + [] + let ``Issue 19550 - non-pipeline empty CE body reports source range``() = + FSharp """ +module Repro19550_08 +type FooBuilder() = + member _.Zero() = fun x -> x + 42 + +let foo = FooBuilder() + +let f : string -> string = foo {} + """ + |> compile + |> shouldFail + |> hasDiagAt 193 (8, 28, 8, 34) + + [] + let ``Issue 19550 - non-CE pipeline mismatch keeps non-zero range``() = + FSharp """ +module Repro19550_09 +let _ = "" |> (fun (x: int) -> x) |> printfn "%d" + """ + |> compile + |> shouldFail + |> hasNoRange0Error + + [] + let ``Issue 19550 - multi-line empty CE body in pipeline``() = + FSharp """ +module Repro19550_10 +type FooBuilder() = + member _.Zero() = fun x -> x + 42 + +let foo = FooBuilder() + +"" |> + foo { + } |> + printfn "%d" + """ + |> compile + |> shouldFail + |> hasDiagAt 193 (9, 4, 10, 5) + + [] + let ``Issue 19550 - empty CE body in match arm pipeline``() = + FSharp """ +module Repro19550_11 +type FooBuilder() = + member _.Zero() = fun x -> x + 42 + +let foo = FooBuilder() + +let r = match 0 with + | _ -> "" |> foo {} |> printfn "%d" + """ + |> compile + |> shouldFail + |> hasDiagAt 193 (9, 22, 9, 28) + + [] + let ``Issue 19550 - both Zero and Yield - empty body picks Zero path``() = + FSharp """ +module Repro19550_12 +type BothBuilder() = + member _.Zero() = fun x -> x + 1 + member _.Yield(x) = fun y -> x + y + +let bb = BothBuilder() + +"" |> bb {} |> printfn "%d" + """ + |> compile + |> shouldFail + |> hasDiagAt 193 (9, 7, 9, 12)