Skip to content

Commit 4390d68

Browse files
authored
[FS-1140] add Boolean-returning and return-type-directed partial active patterns (#16473)
1 parent 77885e6 commit 4390d68

30 files changed

+308
-54
lines changed

docs/release-notes/.FSharp.Compiler.Service/8.0.300.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* Parser recovers on complex primary constructor patterns, better tree representation for primary constructor patterns. ([PR #16425](https://github.com/dotnet/fsharp/pull/16425))
1414
* Name resolution: keep type vars in subsequent checks ([PR #16456](https://github.com/dotnet/fsharp/pull/16456))
1515
* Higher-order-function-based API for working with the untyped abstract syntax tree. ([PR #16462](https://github.com/dotnet/fsharp/pull/16462))
16+
* Allow returning bool instead of unit option for partial active patterns. ([Language suggestion #1041](https://github.com/fsharp/fslang-suggestions/issues/1041), [PR #16473](https://github.com/dotnet/fsharp/pull/16473))
1617

1718
### Changed
1819

docs/release-notes/.Language/preview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Better generic unmanaged structs handling. ([Language suggestion #692](https://github.com/fsharp/fslang-suggestions/issues/692), [PR #12154](https://github.com/dotnet/fsharp/pull/12154))
44
* Bidirectional F#/C# interop for 'unmanaged' constraint. ([PR #12154](https://github.com/dotnet/fsharp/pull/12154))
55
* Make `.Is*` discriminated union properties visible. ([Language suggestion #222](https://github.com/fsharp/fslang-suggestions/issues/222), [PR #16341](https://github.com/dotnet/fsharp/pull/16341))
6+
* Allow returning bool instead of unit option for partial active patterns. ([Language suggestion #1041](https://github.com/fsharp/fslang-suggestions/issues/1041), [PR #16473](https://github.com/dotnet/fsharp/pull/16473))
67

78
### Fixed
89

src/Compiler/Checking/CheckExpressions.fs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5093,7 +5093,11 @@ and TcPatLongIdentActivePatternCase warnOnUpper (cenv: cenv) (env: TcEnv) vFlags
50935093

50945094
if dtys.Length = args.Length + 1 &&
50955095
((isOptionTy g retTy && isUnitTy g (destOptionTy g retTy)) ||
5096-
(isValueOptionTy g retTy && isUnitTy g (destValueOptionTy g retTy))) then
5096+
(isValueOptionTy g retTy && isUnitTy g (destValueOptionTy g retTy))) ||
5097+
// `bool` partial AP always be treated as `unit option`
5098+
// For `val (|P|_|) : _ -> bool`, only allow `match x with | P -> ...`
5099+
// For `val (|P|_|) : _ -> _ -> bool`, only allow `match x with | P parameter -> ...`
5100+
(not apinfo.IsTotal && isBoolTy g retTy) then
50975101
args, SynPat.Const(SynConst.Unit, m)
50985102
else
50995103
List.frontAndBack args
@@ -10752,14 +10756,25 @@ and TcNormalizedBinding declKind (cenv: cenv) env tpenv overallTy safeThisValOpt
1075210756
| Some (apinfo, apOverallTy, _) ->
1075310757
let activePatResTys = NewInferenceTypes g apinfo.ActiveTags
1075410758
let _, apReturnTy = stripFunTy g apOverallTy
10755-
10756-
if isStructRetTy && apinfo.IsTotal then
10757-
errorR(Error(FSComp.SR.tcInvalidStructReturn(), mBinding))
10758-
10759-
if isStructRetTy then
10759+
let apRetTy =
10760+
if apinfo.IsTotal then
10761+
if isStructRetTy then errorR(Error(FSComp.SR.tcInvalidStructReturn(), mBinding))
10762+
ActivePatternReturnKind.RefTypeWrapper
10763+
else
10764+
if isStructRetTy || isValueOptionTy cenv.g apReturnTy then ActivePatternReturnKind.StructTypeWrapper
10765+
elif isBoolTy cenv.g apReturnTy then ActivePatternReturnKind.Boolean
10766+
else ActivePatternReturnKind.RefTypeWrapper
10767+
10768+
match apRetTy with
10769+
| ActivePatternReturnKind.Boolean ->
10770+
checkLanguageFeatureError g.langVersion LanguageFeature.BooleanReturningAndReturnTypeDirectedPartialActivePattern mBinding
10771+
| ActivePatternReturnKind.StructTypeWrapper when not isStructRetTy ->
10772+
checkLanguageFeatureError g.langVersion LanguageFeature.BooleanReturningAndReturnTypeDirectedPartialActivePattern mBinding
10773+
| ActivePatternReturnKind.StructTypeWrapper ->
1076010774
checkLanguageFeatureError g.langVersion LanguageFeature.StructActivePattern mBinding
10775+
| ActivePatternReturnKind.RefTypeWrapper -> ()
1076110776

10762-
UnifyTypes cenv env mBinding (apinfo.ResultType g rhsExpr.Range activePatResTys isStructRetTy) apReturnTy
10777+
UnifyTypes cenv env mBinding (apinfo.ResultType g rhsExpr.Range activePatResTys apRetTy) apReturnTy
1076310778

1076410779
| None ->
1076510780
if isStructRetTy then

src/Compiler/Checking/NameResolution.fs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,18 +87,20 @@ let ActivePatternElemsOfValRef g (vref: ValRef) =
8787
match TryGetActivePatternInfo vref with
8888
| Some apinfo ->
8989

90-
let isStructRetTy =
90+
let retKind =
9191
if apinfo.IsTotal then
92-
false
92+
ActivePatternReturnKind.RefTypeWrapper
9393
else
9494
let _, apReturnTy = stripFunTy g vref.TauType
9595
let hasStructAttribute() =
9696
vref.Attribs
9797
|> List.exists (function
9898
| Attrib(targetsOpt = Some(System.AttributeTargets.ReturnValue)) as a -> IsMatchingFSharpAttribute g g.attrib_StructAttribute a
9999
| _ -> false)
100-
isStructTy g apReturnTy || hasStructAttribute()
101-
apinfo.ActiveTags |> List.mapi (fun i _ -> APElemRef(apinfo, vref, i, isStructRetTy))
100+
if isValueOptionTy g apReturnTy || hasStructAttribute() then ActivePatternReturnKind.StructTypeWrapper
101+
elif isBoolTy g apReturnTy then ActivePatternReturnKind.Boolean
102+
else ActivePatternReturnKind.RefTypeWrapper
103+
apinfo.ActiveTags |> List.mapi (fun i _ -> APElemRef(apinfo, vref, i, retKind))
102104
| None -> []
103105

104106
/// Try to make a reference to a value in a module.

src/Compiler/Checking/PatternMatchCompilation.fs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ type Pattern =
4343
| TPat_as of Pattern * PatternValBinding * range (* note: can be replaced by TPat_var, i.e. equals TPat_conjs([TPat_var; pat]) *)
4444
| TPat_disjs of Pattern list * range
4545
| TPat_conjs of Pattern list * range
46-
| TPat_query of (Expr * TType list * bool * (ValRef * TypeInst) option * int * ActivePatternInfo) * Pattern * range
46+
| TPat_query of (Expr * TType list * ActivePatternReturnKind * (ValRef * TypeInst) option * int * ActivePatternInfo) * Pattern * range
4747
| TPat_unioncase of UnionCaseRef * TypeInst * Pattern list * range
4848
| TPat_exnconstr of TyconRef * Pattern list * range
4949
| TPat_tuple of TupInfo * Pattern list * TType list * range
@@ -618,8 +618,8 @@ let getDiscrimOfPattern (g: TcGlobals) tpinst t =
618618
Some(DecisionTreeTest.UnionCase (c, instTypes tpinst tyargs'))
619619
| TPat_array (args, ty, _m) ->
620620
Some(DecisionTreeTest.ArrayLength (args.Length, ty))
621-
| TPat_query ((activePatExpr, resTys, isStructRetTy, apatVrefOpt, idx, apinfo), _, _m) ->
622-
Some (DecisionTreeTest.ActivePatternCase (activePatExpr, instTypes tpinst resTys, isStructRetTy, apatVrefOpt, idx, apinfo))
621+
| TPat_query ((activePatExpr, resTys, retKind, apatVrefOpt, idx, apinfo), _, _m) ->
622+
Some (DecisionTreeTest.ActivePatternCase (activePatExpr, instTypes tpinst resTys, retKind, apatVrefOpt, idx, apinfo))
623623

624624
| TPat_error range ->
625625
Some (DecisionTreeTest.Error range)
@@ -941,8 +941,8 @@ let rec investigationPoints inpPat =
941941

942942
let rec erasePartialPatterns inpPat =
943943
match inpPat with
944-
| TPat_query ((expr, resTys, isStructRetTy, apatVrefOpt, idx, apinfo), p, m) ->
945-
if apinfo.IsTotal then TPat_query ((expr, resTys, isStructRetTy, apatVrefOpt, idx, apinfo), erasePartialPatterns p, m)
944+
| TPat_query ((expr, resTys, retKind, apatVrefOpt, idx, apinfo), p, m) ->
945+
if apinfo.IsTotal then TPat_query ((expr, resTys, retKind, apatVrefOpt, idx, apinfo), erasePartialPatterns p, m)
946946
else TPat_disjs ([], m) (* always fail *)
947947
| TPat_as (p, x, m) -> TPat_as (erasePartialPatterns p, x, m)
948948
| TPat_disjs (subPats, m) -> TPat_disjs(erasePartials subPats, m)
@@ -1293,15 +1293,20 @@ let CompilePatternBasic
12931293

12941294
// Active pattern matches: create a variable to hold the results of executing the active pattern.
12951295
// If a struct return we continue with an expression for taking the address of that location.
1296-
| EdgeDiscrim(_, DecisionTreeTest.ActivePatternCase(activePatExpr, resTys, isStructRetTy, _apatVrefOpt, _, apinfo), m) :: _ ->
1296+
| EdgeDiscrim(_, DecisionTreeTest.ActivePatternCase(activePatExpr, resTys, retKind, _apatVrefOpt, _, apinfo), m) :: _ ->
12971297

12981298
if not (isNil origInputValTypars) then error(InternalError("Unexpected generalized type variables when compiling an active pattern", m))
12991299

1300-
let resTy = apinfo.ResultType g m resTys isStructRetTy
1300+
let resTy = apinfo.ResultType g m resTys retKind
13011301
let argExpr = GetSubExprOfInput subexpr
13021302
let appExpr = mkApps g ((activePatExpr, tyOfExpr g activePatExpr), [], [argExpr], m)
13031303

1304-
let vOpt, addrExp, _readonly, _writeonly = mkExprAddrOfExprAux g isStructRetTy false NeverMutates appExpr None mMatch
1304+
let mustTakeAddress =
1305+
match retKind with
1306+
| ActivePatternReturnKind.StructTypeWrapper -> true
1307+
| ActivePatternReturnKind.RefTypeWrapper
1308+
| ActivePatternReturnKind.Boolean -> false
1309+
let vOpt, addrExp, _readonly, _writeonly = mkExprAddrOfExprAux g mustTakeAddress false NeverMutates appExpr None mMatch
13051310
match vOpt with
13061311
| None ->
13071312
let v, vExpr = mkCompGenLocal m ("activePatternResult" + string (newUnique())) resTy
@@ -1357,13 +1362,17 @@ let CompilePatternBasic
13571362
// Convert active pattern edges to tests on results data
13581363
let discrim' =
13591364
match discrim with
1360-
| DecisionTreeTest.ActivePatternCase(_pexp, resTys, isStructRetTy, _apatVrefOpt, idx, apinfo) ->
1365+
| DecisionTreeTest.ActivePatternCase(_pexp, resTys, retKind, _apatVrefOpt, idx, apinfo) ->
13611366
let aparity = apinfo.ActiveTags.Length
13621367
let total = apinfo.IsTotal
13631368
if not total && aparity > 1 then
13641369
error(Error(FSComp.SR.patcPartialActivePatternsGenerateOneResult(), m))
13651370

1366-
if not total then DecisionTreeTest.UnionCase(mkAnySomeCase g isStructRetTy, resTys)
1371+
if not total then
1372+
match retKind with
1373+
| ActivePatternReturnKind.Boolean -> DecisionTreeTest.Const(Const.Bool true)
1374+
| ActivePatternReturnKind.RefTypeWrapper -> DecisionTreeTest.UnionCase(mkAnySomeCase g false, resTys)
1375+
| ActivePatternReturnKind.StructTypeWrapper -> DecisionTreeTest.UnionCase(mkAnySomeCase g true, resTys)
13671376
elif aparity <= 1 then DecisionTreeTest.Const(Const.Unit)
13681377
else DecisionTreeTest.UnionCase(mkChoiceCaseRef g m aparity idx, resTys)
13691378
| _ -> discrim
@@ -1435,7 +1444,7 @@ let CompilePatternBasic
14351444
let newActives = removeActive path actives
14361445
match patAtActive with
14371446
| TPat_wild _ | TPat_as _ | TPat_tuple _ | TPat_disjs _ | TPat_conjs _ | TPat_recd _ -> failwith "Unexpected projection pattern"
1438-
| TPat_query ((_, resTys, isStructRetTy, apatVrefOpt, idx, apinfo), p, m) ->
1447+
| TPat_query ((_, resTys, retKind, apatVrefOpt, idx, apinfo), p, m) ->
14391448
if apinfo.IsTotal then
14401449
// Total active patterns always return choice values
14411450
let hasParam = (match apatVrefOpt with None -> true | Some (vref, _) -> doesActivePatternHaveFreeTypars g vref)
@@ -1463,10 +1472,12 @@ let CompilePatternBasic
14631472
if i = iInvestigated then
14641473
let subAccess _j tpinst _ =
14651474
let expr = Option.get inpExprOpt
1466-
if isStructRetTy then
1475+
match retKind with
1476+
| ActivePatternReturnKind.Boolean -> expr
1477+
| ActivePatternReturnKind.StructTypeWrapper ->
14671478
// In this case, the inpExprOpt is already an address-of expression
14681479
mkUnionCaseFieldGetProvenViaExprAddr (expr, mkValueSomeCase g, instTypes tpinst resTys, 0, mExpr)
1469-
else
1480+
| ActivePatternReturnKind.RefTypeWrapper ->
14701481
mkUnionCaseFieldGetUnprovenViaExprAddr (expr, mkSomeCase g, instTypes tpinst resTys, 0, mExpr)
14711482
mkSubFrontiers path subAccess newActives [p] (fun path j -> PathQuery(path, int64 j))
14721483
else

src/Compiler/Checking/PatternMatchCompilation.fsi

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ type Pattern =
2727
| TPat_as of Pattern * PatternValBinding * range
2828
| TPat_disjs of Pattern list * range
2929
| TPat_conjs of Pattern list * range
30-
| TPat_query of (Expr * TType list * bool * (ValRef * TypeInst) option * int * ActivePatternInfo) * Pattern * range
30+
| TPat_query of
31+
(Expr * TType list * ActivePatternReturnKind * (ValRef * TypeInst) option * int * ActivePatternInfo) *
32+
Pattern *
33+
range
3134
| TPat_unioncase of UnionCaseRef * TypeInst * Pattern list * range
3235
| TPat_exnconstr of TyconRef * Pattern list * range
3336
| TPat_tuple of TupInfo * Pattern list * TType list * range

src/Compiler/FSComp.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,6 +1593,7 @@ featurePreferExtensionMethodOverPlainProperty,"prefer extension method over plai
15931593
featureWarningIndexedPropertiesGetSetSameType,"Indexed properties getter and setter must have the same type"
15941594
featureChkTailCallAttrOnNonRec,"Raises warnings if the 'TailCall' attribute is used on non-recursive functions."
15951595
featureUnionIsPropertiesVisible,"Union case test properties"
1596+
featureBooleanReturningAndReturnTypeDirectedPartialActivePattern,"Boolean-returning and return-type-directed partial active patterns"
15961597
3354,tcNotAFunctionButIndexerNamedIndexingNotYetEnabled,"This value supports indexing, e.g. '%s.[index]'. The syntax '%s[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation."
15971598
3354,tcNotAFunctionButIndexerIndexingNotYetEnabled,"This expression supports indexing, e.g. 'expr.[index]'. The syntax 'expr[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation."
15981599
3355,tcNotAnIndexerNamedIndexingNotYetEnabled,"The value '%s' is not a function and does not support index notation."

src/Compiler/Facilities/LanguageFeatures.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ type LanguageFeature =
8484
| PreferExtensionMethodOverPlainProperty
8585
| WarningIndexedPropertiesGetSetSameType
8686
| WarningWhenTailCallAttrOnNonRec
87+
| BooleanReturningAndReturnTypeDirectedPartialActivePattern
8788

8889
/// LanguageVersion management
8990
type LanguageVersion(versionText) =
@@ -195,6 +196,7 @@ type LanguageVersion(versionText) =
195196
LanguageFeature.WarningIndexedPropertiesGetSetSameType, previewVersion
196197
LanguageFeature.WarningWhenTailCallAttrOnNonRec, previewVersion
197198
LanguageFeature.UnionIsPropertiesVisible, previewVersion
199+
LanguageFeature.BooleanReturningAndReturnTypeDirectedPartialActivePattern, previewVersion
198200
]
199201

200202
static let defaultLanguageVersion = LanguageVersion("default")
@@ -336,6 +338,8 @@ type LanguageVersion(versionText) =
336338
| LanguageFeature.PreferExtensionMethodOverPlainProperty -> FSComp.SR.featurePreferExtensionMethodOverPlainProperty ()
337339
| LanguageFeature.WarningIndexedPropertiesGetSetSameType -> FSComp.SR.featureWarningIndexedPropertiesGetSetSameType ()
338340
| LanguageFeature.WarningWhenTailCallAttrOnNonRec -> FSComp.SR.featureChkTailCallAttrOnNonRec ()
341+
| LanguageFeature.BooleanReturningAndReturnTypeDirectedPartialActivePattern ->
342+
FSComp.SR.featureBooleanReturningAndReturnTypeDirectedPartialActivePattern ()
339343

340344
/// Get a version string associated with the given feature.
341345
static member GetFeatureVersionString feature =

src/Compiler/Facilities/LanguageFeatures.fsi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type LanguageFeature =
7575
| PreferExtensionMethodOverPlainProperty
7676
| WarningIndexedPropertiesGetSetSameType
7777
| WarningWhenTailCallAttrOnNonRec
78+
| BooleanReturningAndReturnTypeDirectedPartialActivePattern
7879

7980
/// LanguageVersion management
8081
type LanguageVersion =

src/Compiler/TypedTree/TypedTree.fs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4565,6 +4565,17 @@ type DecisionTreeCase =
45654565
member x.DebugText = x.ToString()
45664566

45674567
override x.ToString() = sprintf "DecisionTreeCase(...)"
4568+
4569+
[<Struct; NoComparison; NoEquality; RequireQualifiedAccess>]
4570+
type ActivePatternReturnKind =
4571+
| RefTypeWrapper
4572+
| StructTypeWrapper
4573+
| Boolean
4574+
member this.IsStruct with get () =
4575+
match this with
4576+
| RefTypeWrapper -> false
4577+
| StructTypeWrapper
4578+
| Boolean -> true
45684579

45694580
[<NoEquality; NoComparison; RequireQualifiedAccess (*; StructuredFormatDisplay("{DebugText}") *) >]
45704581
type DecisionTreeTest =
@@ -4585,20 +4596,20 @@ type DecisionTreeTest =
45854596
/// Test if the input to a decision tree is an instance of the given type
45864597
| IsInst of source: TType * target: TType
45874598

4588-
/// Test.ActivePatternCase(activePatExpr, activePatResTys, isStructRetTy, activePatIdentity, idx, activePatInfo)
4599+
/// Test.ActivePatternCase(activePatExpr, activePatResTys, activePatRetKind, activePatIdentity, idx, activePatInfo)
45894600
///
45904601
/// Run the active pattern and bind a successful result to a
45914602
/// variable in the remaining tree.
45924603
/// activePatExpr -- The active pattern function being called, perhaps applied to some active pattern parameters.
45934604
/// activePatResTys -- The result types (case types) of the active pattern.
4594-
/// isStructRetTy -- Is the active pattern a struct return
4605+
/// activePatRetKind -- Indicating what is returning from the active pattern
45954606
/// activePatIdentity -- The value and the types it is applied to. If there are any active pattern parameters then this is empty.
45964607
/// idx -- The case number of the active pattern which the test relates to.
45974608
/// activePatternInfo -- The extracted info for the active pattern.
45984609
| ActivePatternCase of
45994610
activePatExpr: Expr *
46004611
activePatResTys: TTypes *
4601-
isStructRetTy: bool *
4612+
activePatRetKind: ActivePatternReturnKind *
46024613
activePatIdentity: (ValRef * TypeInst) option *
46034614
idx: int *
46044615
activePatternInfo: ActivePatternInfo
@@ -4667,7 +4678,7 @@ type ActivePatternElemRef =
46674678
activePatternInfo: ActivePatternInfo *
46684679
activePatternVal: ValRef *
46694680
caseIndex: int *
4670-
isStructRetTy: bool
4681+
activePatRetKind: ActivePatternReturnKind
46714682

46724683
/// Get the full information about the active pattern being referred to
46734684
member x.ActivePatternInfo = (let (APElemRef(info, _, _, _)) = x in info)
@@ -4676,7 +4687,7 @@ type ActivePatternElemRef =
46764687
member x.ActivePatternVal = (let (APElemRef(_, vref, _, _)) = x in vref)
46774688

46784689
/// Get a reference to the value for the active pattern being referred to
4679-
member x.IsStructReturn = (let (APElemRef(_, _, _, isStructRetTy)) = x in isStructRetTy)
4690+
member x.ActivePatternRetKind = (let (APElemRef(_, _, _, activePatRetKind)) = x in activePatRetKind)
46804691

46814692
/// Get the index of the active pattern element within the overall active pattern
46824693
member x.CaseIndex = (let (APElemRef(_, _, n, _)) = x in n)

0 commit comments

Comments
 (0)