Skip to content

Commit f2b8306

Browse files
authored
extend the detection of CPS-like expressions to allow warning for List.collect etc. (#16027)
1 parent d16af0e commit f2b8306

File tree

2 files changed

+79
-6
lines changed

2 files changed

+79
-6
lines changed

src/Compiler/Checking/TailCallChecks.fs

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,31 @@ and CheckForNonTailRecCall (cenv: cenv) expr (tailCall: TailCall) =
216216
| _ -> ()
217217

218218
/// Check call arguments, including the return argument.
219-
and CheckCall cenv args ctxts = CheckExprs cenv args ctxts TailCall.No
219+
and CheckCall cenv args ctxts (tailCall: TailCall) =
220+
// detect CPS-like expressions
221+
let rec (|IsAppInLambdaBody|_|) e =
222+
match stripDebugPoints e with
223+
| Expr.TyLambda (bodyExpr = bodyExpr)
224+
| Expr.Lambda (bodyExpr = bodyExpr) ->
225+
match (stripDebugPoints bodyExpr) with
226+
| Expr.App _ -> Some(TailCall.YesFromExpr cenv.g e)
227+
| IsAppInLambdaBody t -> Some t
228+
| _ -> None
229+
| _ -> None
230+
231+
// if we haven't already decided this is no tail call, try to detect CPS-like expressions
232+
let tailCall =
233+
if tailCall = TailCall.No then
234+
tailCall
235+
else
236+
args
237+
|> List.tryPick (fun a ->
238+
match a with
239+
| IsAppInLambdaBody t -> Some t
240+
| _ -> None)
241+
|> Option.defaultValue TailCall.No
242+
243+
CheckExprs cenv args ctxts tailCall
220244

221245
/// Check call arguments, including the return argument. The receiver argument is handled differently.
222246
and CheckCallWithReceiver cenv args ctxts =
@@ -330,7 +354,25 @@ and CheckExpr (cenv: cenv) origExpr (ctxt: PermitByRefExpr) (tailCall: TailCall)
330354
| TypeDefOfExpr g ty when isVoidTy g ty -> ()
331355

332356
// Check an application
333-
| Expr.App (f, _fty, _tyargs, argsl, _m) -> CheckApplication cenv (f, argsl) tailCall
357+
| Expr.App (f, _fty, _tyargs, argsl, _m) ->
358+
// detect expressions like List.collect
359+
let checkArgForLambdaWithAppOfMustTailCall e =
360+
match stripDebugPoints e with
361+
| Expr.TyLambda (bodyExpr = bodyExpr)
362+
| Expr.Lambda (bodyExpr = bodyExpr) ->
363+
match bodyExpr with
364+
| Expr.App (ValUseAtApp (vref, _valUseFlags), _formalType, _typeArgs, _exprs, _range) ->
365+
cenv.mustTailCall.Contains vref.Deref
366+
| _ -> false
367+
| _ -> false
368+
369+
let tailCall =
370+
if argsl |> List.exists checkArgForLambdaWithAppOfMustTailCall then
371+
TailCall.No
372+
else
373+
tailCall
374+
375+
CheckApplication cenv (f, argsl) tailCall
334376

335377
| Expr.Lambda (_, _, _, argvs, _, m, bodyTy) -> CheckLambda cenv expr (argvs, m, bodyTy) tailCall
336378

@@ -388,7 +430,7 @@ and CheckApplication cenv (f, argsl) (tailCall: TailCall) : unit =
388430
if hasReceiver then
389431
CheckCallWithReceiver cenv argsl ctxts
390432
else
391-
CheckCall cenv argsl ctxts
433+
CheckCall cenv argsl ctxts tailCall
392434

393435
and CheckLambda cenv expr (argvs, m, bodyTy) (tailCall: TailCall) =
394436
let valReprInfo =
@@ -470,12 +512,12 @@ and CheckExprOp cenv (op, tyargs, args, m) ctxt : unit =
470512
if hasReceiver then
471513
CheckCallWithReceiver cenv args argContexts
472514
else
473-
CheckCall cenv args argContexts
515+
CheckCall cenv args argContexts TailCall.No
474516
| _ ->
475517
if hasReceiver then
476518
CheckCallWithReceiver cenv args argContexts
477519
else
478-
CheckCall cenv args argContexts
520+
CheckCall cenv args argContexts TailCall.No
479521

480522
| TOp.Tuple tupInfo, _, _ when not (evalTupInfoIsStruct tupInfo) ->
481523
match ctxt with
@@ -604,7 +646,7 @@ and CheckLambdas
604646
// allow byref to occur as return position for byref-typed top level function or method
605647
CheckExprPermitReturnableByRef cenv body
606648
else
607-
CheckExprNoByrefs cenv (TailCall.YesFromExpr cenv.g body) body // TailCall.Yes for CPS
649+
CheckExprNoByrefs cenv tailCall body
608650

609651
// This path is for expression bindings that are not actually lambdas
610652
| _ ->

tests/FSharp.Compiler.ComponentTests/ErrorMessages/TailCallAttribute.fs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,37 @@ namespace N
884884
Message =
885885
"The member or function 'findMax' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way." }
886886
]
887+
888+
[<FSharp.Test.FactForNETCOREAPP>]
889+
let ``Warn for non tail-rec traversal with List.collect`` () =
890+
"""
891+
namespace N
892+
893+
module M =
894+
895+
type Tree =
896+
| Leaf of int
897+
| Node of Tree list
898+
899+
[<TailCall>]
900+
let rec loop tree =
901+
match tree with
902+
| Leaf n -> [ n ]
903+
| Node branches -> branches |> List.collect loop
904+
"""
905+
|> FSharp
906+
|> withLangVersionPreview
907+
|> compile
908+
|> shouldFail
909+
|> withResults [
910+
{ Error = Warning 3569
911+
Range = { StartLine = 14
912+
StartColumn = 57
913+
EndLine = 14
914+
EndColumn = 61 }
915+
Message =
916+
"The member or function 'loop' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way." }
917+
]
887918

888919
[<FSharp.Test.FactForNETCOREAPP>]
889920
let ``Don't warn for Continuation Passing Style func using [<TailCall>] func in continuation lambda`` () =

0 commit comments

Comments
 (0)