Skip to content

Commit

Permalink
Resolve issue with implicit yields requiring Zero (dotnet#10556)
Browse files Browse the repository at this point in the history
  • Loading branch information
laenas authored and nosami committed Feb 22, 2021
1 parent 0f373f0 commit 02fa6d0
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 5 deletions.
8 changes: 7 additions & 1 deletion src/fsharp/CheckComputationExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -949,8 +949,14 @@ let TcComputationExpression cenv env overallTy tpenv (mWhole, interpExpr: Expr,
| SynExpr.Paren (_, _, _, m) ->
error(Error(FSComp.SR.tcConstructIsAmbiguousInComputationExpression(), m))

// In some cases the node produced by `mkSynCall "Zero" m []` may be discarded in the case
// of implicit yields - for example "list { 1; 2 }" when each expression checks as an implicit yield.
// If it is not discarded, the syntax node will later be checked and the existence/non-existence of the Zero method
// will be checked/reported appropriately (though the error message won't mention computation expressions
// like our other error messages for missing methods).
| SynExpr.ImplicitZero m ->
if isNil (TryFindIntrinsicOrExtensionMethInfo ResultCollectionSettings.AtMostOneResult cenv env m ad "Zero" builderTy) then error(Error(FSComp.SR.tcRequireBuilderMethod("Zero"), m))
if (not enableImplicitYield) &&
isNil (TryFindIntrinsicOrExtensionMethInfo ResultCollectionSettings.AtMostOneResult cenv env m ad "Zero" builderTy) then error(Error(FSComp.SR.tcRequireBuilderMethod("Zero"), m))
Some (translatedCtxt (mkSynCall "Zero" m []))

| OptionalSequential (JoinOrGroupJoinOrZipClause (_, _, _, _, _, mClause), _)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<Compile Include="Language\XmlComments.fs" />
<Compile Include="Language\CompilerDirectiveTests.fs" />
<Compile Include="Language\CodeQuotationTests.fs" />
<Compile Include="Language\ComputationExpressionTests.fs" />
<Compile Include="ConstraintSolver\PrimitiveConstraints.fs" />
<Compile Include="ConstraintSolver\MemberConstraints.fs" />
<Compile Include="Interop\SimpleInteropTests.fs" />
Expand All @@ -59,8 +60,4 @@
<ProjectReference Include="$(FSharpSourcesRoot)\fsharp\FSharp.Core\FSharp.Core.fsproj" />
<ProjectReference Include="$(FSharpTestsRoot)\FSharp.Test.Utilities\FSharp.Test.Utilities.fsproj" />
</ItemGroup>

<ItemGroup>
<Folder Include="NewFolder\" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.

namespace FSharp.Compiler.ComponentTests.Language

open Xunit
open FSharp.Test.Utilities.Compiler

module ComputationExpressionTests =
[<Fact>]
let ``A CE not using Zero does not require Zero``() =
FSharp """
module ComputationExpressionTests
type ListBuilder () =
member __.Combine (a: List<'T>, b) = a @ b
member __.Yield x = List.singleton x
member __.Delay expr = expr () : List<'T>
let lb = ListBuilder ()
let x = lb {1; 2;}
"""
|> compile
|> shouldSucceed
|> ignore

[<Fact>]
let ``A CE explicitly using Zero fails without a defined Zero``() =
FSharp """
module ComputationExpressionTests
type ListBuilder () =
member __.Combine (a: List<'T>, b) = a @ b
member __.Yield x = List.singleton x
member __.Delay expr = expr () : List<'T>
let lb = ListBuilder ()
let x = lb {1; 2;()}
"""
|> compile
|> shouldFail
|> withSingleDiagnostic (Error 39, Line 10, Col 18, Line 10, Col 20, "The type 'ListBuilder' does not define the field, constructor or member 'Zero'.")
|> ignore

[<Fact>]
let ``A CE explicitly using Zero succeeds with a defined Zero``() =
FSharp """
module ComputationExpressionTests
type ListBuilder () =
member __.Zero () = []
member __.Combine (a: List<'T>, b) = a @ b
member __.Yield x = List.singleton x
member __.Delay expr = expr () : List<'T>
let lb = ListBuilder ()
let x = lb {1; 2;()}
"""
|> compile
|> shouldSucceed
|> ignore

[<Fact>]
let ``A CE with a complete if-then expression does not require Zero``() =
FSharp """
module ComputationExpressionTests
type ListBuilder () =
member __.Combine (a: List<'T>, b) = a @ b
member __.Yield x = List.singleton x
member __.Delay expr = expr () : List<'T>
let lb = ListBuilder ()
let x = lb {1; 2; if true then 3 else 4;}
"""
|> compile
|> shouldSucceed
|> ignore

[<Fact>]
let ``A CE with a missing/empty else branch implicitly requires Zero``() =
FSharp """
module ComputationExpressionTests
type ListBuilder () =
member __.Combine (a: List<'T>, b) = a @ b
member __.Yield x = List.singleton x
member __.Delay expr = expr () : List<'T>
let lb = ListBuilder ()
let x = lb {1; 2; if true then 3;}
"""
|> compile
|> shouldFail
|> withSingleDiagnostic (Error 708, Line 10, Col 19, Line 10, Col 31, "This control construct may only be used if the computation expression builder defines a 'Zero' method")
|> ignore

0 comments on commit 02fa6d0

Please sign in to comment.