From 0356d321ea15fc963e68d272cd5785363a30e086 Mon Sep 17 00:00:00 2001 From: Rodrigo Vidal Date: Wed, 6 May 2026 00:31:24 +0100 Subject: [PATCH] Fix CE builder method lookup and add diagnostics regressions Co-authored-by: multica-agent --- .../CheckComputationExpressions.fs | 41 ++++++++++++------ .../ComputationExpressions.fs | 42 +++++++++++++++++++ 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index 3ab446a6687..8c6e937c198 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -47,6 +47,7 @@ type ComputationExpressionContext<'a> = cenv: TcFileState env: TcEnv tpenv: UnscopedTyparEnv + tryFindBuilderMethod: string -> MethInfo list customOperationMethodsIndexedByKeyword: IDictionary * MethInfo>> customOperationMethodsIndexedByMethodName: @@ -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 @@ -999,7 +995,8 @@ let inline addVarsToVarSpace (varSpace: LazyWithContext ) 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 @@ -2958,6 +2955,26 @@ let TcComputationExpression (cenv: TcFileState) env (overallTy: OverallTy) tpenv let builderValName = CompilerGeneratedName "builder" let mBuilderVal = interpExpr.Range + let builderMethodCache = Dictionary() + + 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 = @@ -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 @@ -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 @@ -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 diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs index 7607e632493..c8b04d2ac47 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Expressions/ComputationExpressions/ComputationExpressions.fs @@ -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'.") ] +[] +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" + +[] +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 [] let ``Issue 3783 - Mutually recursive computation expression should not raise NullReferenceException`` () =