Skip to content

Commit cddeab2

Browse files
committed
Enhance active pattern case usage detection for partial patterns in match expressions
1 parent dfcd2d8 commit cddeab2

File tree

2 files changed

+72
-23
lines changed

2 files changed

+72
-23
lines changed

src/FsAutoComplete.Core/Commands.fs

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,22 @@ module Commands =
9898
let rec walkPat (pat: SynPat) =
9999
seq {
100100
match pat with
101-
| SynPat.LongIdent(longDotId = SynLongIdent(id = idents); argPats = args) ->
101+
| SynPat.LongIdent(longDotId = synLongIdent; argPats = args) ->
102102
// Check if this is a potential case usage
103-
match idents with
104-
| [] -> ()
105-
| [ singleIdent ] when List.contains singleIdent.idText caseNames ->
106-
// Single identifier that matches a case name
107-
yield singleIdent.idRange
108-
| multipleIdents ->
109-
// Qualified identifier (e.g., MyModule.ParseInt)
110-
let lastIdent = List.last multipleIdents
111-
112-
if List.contains lastIdent.idText caseNames then
113-
yield lastIdent.idRange
103+
match synLongIdent with
104+
| SynLongIdent(id = idents) ->
105+
match idents with
106+
| [] -> ()
107+
| [ singleIdent ] when List.contains singleIdent.idText caseNames ->
108+
// Single identifier that matches a case name
109+
yield singleIdent.idRange
110+
| multipleIdents ->
111+
// Qualified identifier (e.g., MyModule.ParseInt)
112+
let lastIdent = List.last multipleIdents
113+
114+
if List.contains lastIdent.idText caseNames then
115+
// Return the full qualified range to match what FCS returns
116+
yield synLongIdent.Range
114117

115118
// Recursively check arguments
116119
match args with
@@ -184,11 +187,9 @@ module Commands =
184187
| SynSimplePat.Typed(pat, _, _)
185188
| SynSimplePat.Attrib(pat, _, _) ->
186189
match pat with
187-
| SynSimplePat.Id(ident, _, _, _, _, _) when List.contains ident.idText caseNames ->
188-
yield ident.idRange
190+
| SynSimplePat.Id(ident, _, _, _, _, _) when List.contains ident.idText caseNames -> yield ident.idRange
189191
| _ -> ()
190-
| SynSimplePat.Id(ident, _, _, _, _, _) when List.contains ident.idText caseNames ->
191-
yield ident.idRange
192+
| SynSimplePat.Id(ident, _, _, _, _, _) when List.contains ident.idText caseNames -> yield ident.idRange
192193
| _ -> ()
193194

194195
yield! walkExpr body
@@ -215,6 +216,7 @@ module Commands =
215216
| Some e3 -> yield! walkExpr e3
216217
| None -> ()
217218
| SynExpr.Paren(expr, _, _, _) -> yield! walkExpr expr
219+
| SynExpr.Typed(expr = expr) -> yield! walkExpr expr
218220
// Computation expressions (seq, async, task, etc.)
219221
| SynExpr.ComputationExpr(expr = expr) -> yield! walkExpr expr
220222
// Array or list expressions
@@ -932,7 +934,14 @@ module Commands =
932934
asyncResult {
933935
let symbol = symbolUse.Symbol
934936

935-
let symbolNameCore = symbol.DisplayNameCore
937+
let symbolNameCore =
938+
match symbol with
939+
| :? FSharpMemberOrFunctionOrValue as mfv when
940+
mfv.IsActivePattern
941+
|| (mfv.DisplayName.StartsWith("(|") && mfv.DisplayName.EndsWith("|)"))
942+
->
943+
mfv.DisplayName.TrimStart('(', '|').TrimEnd(')', '|', '_')
944+
| _ -> symbol.DisplayNameCore
936945

937946
let tryAdjustRanges (text: IFSACSourceText, ranges: seq<Range>) =
938947
let ranges = ranges |> Seq.map (fun range -> range.NormalizeDriveLetterCasing())
@@ -982,11 +991,26 @@ module Commands =
982991
| :? FSharpActivePatternCase as foundApc -> foundApc.Name = apc.Name
983992
| _ -> false)
984993

985-
filtered, []
986-
| :? FSharpMemberOrFunctionOrValue as mfv when mfv.IsActivePattern ->
994+
// For partial active patterns in .fsx files, FCS may not return case usages
995+
// We walk the AST and deduplicate with what FCS found
996+
let isPartialPattern = apc.Group.IsTotal |> not
997+
998+
let caseUsageRanges =
999+
if isPartialPattern then
1000+
findPartialActivePatternCaseUsages [ apc.Name ] tyRes.GetParseResults
1001+
else
1002+
// Complete patterns - FCS handles these correctly
1003+
[]
1004+
1005+
filtered, caseUsageRanges
1006+
| :? FSharpMemberOrFunctionOrValue as mfv when
1007+
mfv.IsActivePattern
1008+
|| (mfv.DisplayName.StartsWith("(|") && mfv.DisplayName.EndsWith("|)"))
1009+
->
9871010
// Querying from the active pattern function declaration
9881011
// For partial active patterns like (|ParseInt|_|), include case usages in match expressions
9891012
// For complete active patterns like (|Even|Odd|), only return the function declaration
1013+
// Note: IsActivePattern is true for direct definitions, but let-bound values need DisplayName check
9901014

9911015
let patternDisplayName = mfv.DisplayName
9921016
// DisplayName includes parens: "(|ParseInt|_|)" so we need to check for "|_|)" not just "|_|"
@@ -1003,7 +1027,8 @@ module Commands =
10031027
|> Array.filter (fun s -> not (String.IsNullOrWhiteSpace(s)))
10041028
|> Array.toList
10051029

1006-
let caseUsageRanges = findPartialActivePatternCaseUsages caseNames tyRes.GetParseResults
1030+
let caseUsageRanges =
1031+
findPartialActivePatternCaseUsages caseNames tyRes.GetParseResults
10071032

10081033
// For partial patterns, FCS doesn't include case usages in pattern matches
10091034
// We found them by walking the AST, so we return them as additional ranges
@@ -1013,7 +1038,9 @@ module Commands =
10131038
let ranges =
10141039
let baseRanges = symbolUses |> Seq.map (fun u -> u.Range)
10151040
// Add any additional ranges we found from walking the AST for partial active patterns
1041+
// Deduplicate based on range start and end positions
10161042
Seq.append baseRanges (Seq.ofList additionalRangesForPartialPatterns)
1043+
|> Seq.distinctBy (fun r -> r.Start, r.End)
10171044
// Note: tryAdjustRanges is designed to only be able to fail iff `errorOnFailureToFixRange` is `true`
10181045
let! ranges = tryAdjustRanges (text, ranges)
10191046
let ranges = dict [ (text.FileName, Seq.toArray ranges) ]

src/FsAutoComplete/LspServers/AdaptiveServerState.fs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2180,8 +2180,7 @@ type AdaptiveState
21802180
| CompilerProjectOption.TransparentCompiler snap ->
21812181
checker.FindReferencesForSymbolInFile(file, snap, symbol)
21822182
// `FSharpChecker.FindBackgroundReferencesInFile` only works with existing files
2183-
| CompilerProjectOption.BackgroundCompiler opts ->
2184-
checker.FindReferencesForSymbolInFile(file, opts, symbol)
2183+
| CompilerProjectOption.BackgroundCompiler opts -> checker.FindReferencesForSymbolInFile(file, opts, symbol)
21852184
else
21862185
// untitled script files
21872186
async {
@@ -2195,8 +2194,13 @@ type AdaptiveState
21952194

21962195
// For partial active patterns, also find case usages in match expressions
21972196
match (symbol: FSharpSymbol) with
2198-
| :? FSharpMemberOrFunctionOrValue as mfv when mfv.IsActivePattern ->
2197+
| :? FSharpMemberOrFunctionOrValue as mfv when
2198+
mfv.IsActivePattern
2199+
|| (mfv.DisplayName.StartsWith("(|") && mfv.DisplayName.EndsWith("|)"))
2200+
->
21992201
let patternDisplayName = mfv.DisplayName
2202+
// Check if it's a partial active pattern (contains |_|)
2203+
// Note: IsActivePattern is true for direct definitions, but let-bound values need DisplayName check
22002204
let isPartialActivePattern = patternDisplayName.Contains("|_|")
22012205

22022206
if isPartialActivePattern then
@@ -2215,6 +2219,24 @@ type AdaptiveState
22152219
return Seq.append baseRanges caseUsageRanges
22162220
else
22172221
return baseRanges
2222+
| :? FSharpActivePatternCase as apc ->
2223+
// When user clicks on a specific active pattern case usage (e.g., LetterOrDigit in a match expression),
2224+
// FCS returns all usages correctly in project files, but may not in .fsx files for partial patterns
2225+
let isPartialPattern = apc.Group.IsTotal |> not
2226+
2227+
if isPartialPattern then
2228+
match! forceGetOpenFileTypeCheckResultsStale file with
2229+
| Error _ -> return baseRanges
2230+
| Ok tyRes ->
2231+
let caseUsageRanges =
2232+
Commands.findPartialActivePatternCaseUsages [ apc.Name ] tyRes.GetParseResults
2233+
2234+
// Combine and deduplicate
2235+
return
2236+
Seq.append baseRanges caseUsageRanges
2237+
|> Seq.distinctBy (fun r -> r.Start, r.End)
2238+
else
2239+
return baseRanges
22182240
| _ -> return baseRanges
22192241
}
22202242

0 commit comments

Comments
 (0)