Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 29 additions & 12 deletions src/Compiler/Checking/Expressions/CheckComputationExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type ComputationExpressionContext<'a> =
cenv: TcFileState
env: TcEnv
tpenv: UnscopedTyparEnv
tryFindBuilderMethod: string -> MethInfo list
customOperationMethodsIndexedByKeyword:
IDictionary<string, list<string * bool * bool * bool * bool * bool * bool * option<string> * MethInfo>>
customOperationMethodsIndexedByMethodName:
Expand Down Expand Up @@ -178,11 +179,6 @@ let transferVarSpaceReferences (expr: Expr) =
for v in vals do
v.SetHasBeenReferenced()

let hasMethInfo nm cenv env mBuilderVal ad builderTy =
match TryFindIntrinsicOrExtensionMethInfo ResultCollectionSettings.AtMostOneResult cenv env mBuilderVal ad nm builderTy with
| [] -> false
| _ -> true

let getCustomOperationMethods (cenv: TcFileState) (env: TcEnv) ad mBuilderVal builderTy =
let allMethInfos =
AllMethInfosOfTypeInScope
Expand Down Expand Up @@ -999,7 +995,8 @@ let inline addVarsToVarSpace (varSpace: LazyWithContext<Val list * TcEnv, range>
)

let tryFindBuilderMethod (ceenv: ComputationExpressionContext<_>) (m: range) (methodName: string) =
TryFindIntrinsicOrExtensionMethInfo ResultCollectionSettings.AtMostOneResult ceenv.cenv ceenv.env m ceenv.ad methodName ceenv.builderTy
let _ = m
ceenv.tryFindBuilderMethod methodName

let hasBuilderMethod ceenv m methodName =
tryFindBuilderMethod ceenv m methodName |> isNil |> not
Expand Down Expand Up @@ -2958,6 +2955,26 @@ let TcComputationExpression (cenv: TcFileState) env (overallTy: OverallTy) tpenv

let builderValName = CompilerGeneratedName "builder"
let mBuilderVal = interpExpr.Range
let builderMethodCache = Dictionary<string, MethInfo list>()

let tryFindBuilderMethodByName methodName =
match builderMethodCache.TryGetValue(methodName) with
| true, methInfos -> methInfos
| false, _ ->
let methInfos =
TryFindIntrinsicOrExtensionMethInfo
ResultCollectionSettings.AllResults
cenv
env
mBuilderVal
ad
methodName
builderTy

builderMethodCache[methodName] <- methInfos
methInfos

let hasBuilderMethodByName methodName = tryFindBuilderMethodByName methodName |> isNil |> not

// Give bespoke error messages for the FSharp.Core "query" builder
let isQuery =
Expand All @@ -2971,11 +2988,10 @@ let TcComputationExpression (cenv: TcFileState) env (overallTy: OverallTy) tpenv
valRefEq cenv.g vref cenv.g.query_value_vref
| _ -> false

let sourceMethInfo =
TryFindIntrinsicOrExtensionMethInfo ResultCollectionSettings.AtMostOneResult cenv env mBuilderVal ad "Source" builderTy
let sourceMethInfo = tryFindBuilderMethodByName "Source"

/// Decide if the builder is an auto-quote builder
let isAutoQuote = hasMethInfo "Quote" cenv env mBuilderVal ad builderTy
let isAutoQuote = hasBuilderMethodByName "Quote"

let customOperationMethods =
getCustomOperationMethods cenv env ad mBuilderVal builderTy
Expand Down Expand Up @@ -3014,9 +3030,9 @@ let TcComputationExpression (cenv: TcFileState) env (overallTy: OverallTy) tpenv
// positions as 'yield'. 'yield!' may be present in the computation expression.
let enableImplicitYield =
cenv.g.langVersion.SupportsFeature LanguageFeature.ImplicitYield
&& (hasMethInfo "Yield" cenv env mBuilderVal ad builderTy
&& hasMethInfo "Combine" cenv env mBuilderVal ad builderTy
&& hasMethInfo "Delay" cenv env mBuilderVal ad builderTy
&& (hasBuilderMethodByName "Yield"
&& hasBuilderMethodByName "Combine"
&& hasBuilderMethodByName "Delay"
&& YieldFree cenv comp)

let origComp = comp
Expand All @@ -3026,6 +3042,7 @@ let TcComputationExpression (cenv: TcFileState) env (overallTy: OverallTy) tpenv
cenv = cenv
env = env
tpenv = tpenv
tryFindBuilderMethod = tryFindBuilderMethodByName
customOperationMethodsIndexedByKeyword = customOperationMethodsIndexedByKeyword
customOperationMethodsIndexedByMethodName = customOperationMethodsIndexedByMethodName
sourceMethInfo = sourceMethInfo
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,48 @@ module LetUseBangTests =
"This construct may only be used within computation expressions. To return a value from an ordinary function simply write the expression without 'return'.")
]

[<Fact>]
let ``Computation expression method lookup uses builder members, not top-level values`` () =
FSharp """
let Return x = x

type Builder() =
member _.Bind(x, f) = f x

let builder = Builder()

let _ =
builder {
return 1
}
"""
|> asExe
|> compile
|> shouldFail
|> withErrorCode 708
|> withDiagnosticMessageMatches "'Return' method"

[<Fact>]
let ``Missing Delay on builder gives FS0708`` () =
FSharp """
type Builder() =
member _.While(guard, body) = ()
member _.Zero() = ()

let builder = Builder()

let _ =
builder {
while false do
()
}
"""
|> asExe
|> compile
|> shouldFail
|> withErrorCode 708
|> withDiagnosticMessageMatches "'Delay' method"

// https://github.com/dotnet/fsharp/issues/3783
[<Fact>]
let ``Issue 3783 - Mutually recursive computation expression should not raise NullReferenceException`` () =
Expand Down
Loading