Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend FS25 codefix to allow generating match cases from scratch #1309

Merged
merged 5 commits into from
Sep 4, 2024
Merged
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
21 changes: 19 additions & 2 deletions src/FsAutoComplete.Core/UnionPatternMatchCaseGenerator.fs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,19 @@ let private tryFindPatternMatchExprInParsedInput (pos: Position) (parsedInput: P
| _ -> None
else
None)
|> Option.orElseWith (fun () ->
if synMatchClauseList.IsEmpty then
match debugPoint with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason you went with this extra check for the debug point.
I don't think it matters, checking if synMatchClauseList.IsEmpty probably is enough.

Copy link
Contributor Author

@gbtb gbtb Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I copied this check from preceding "branch" of orElseWith call chain. Honestly, I don't know what DebugPointAtBinding is here for 😅

| DebugPointAtBinding.Yes range ->
{ MatchWithOrFunctionRange = range
Expr = matchExpr
Clauses = [] }
|> Some
| _ -> None
else
None

)

| SynExpr.App(_exprAtomicFlag, _isInfix, synExpr1, synExpr2, _range) ->
List.tryPick walkExpr [ synExpr1; synExpr2 ]
Expand Down Expand Up @@ -496,20 +509,24 @@ let tryFindCaseInsertionParamsAtPos (codeGenService: ICodeGenerationService) pos
let! insertionParams = tryFindInsertionParams codeGenService document patMatchExpr
return patMatchExpr, insertionParams
else
return! None
return
patMatchExpr,
{ InsertionPos = patMatchExpr.Expr.Range.End
IndentColumn = patMatchExpr.Expr.Range.Start.Column }
}

let tryFindUnionDefinitionFromPos (codeGenService: ICodeGenerationService) pos document =
asyncOption {
let! patMatchExpr, insertionParams = tryFindCaseInsertionParamsAtPos codeGenService pos document
let! _, symbolUse = codeGenService.GetSymbolAndUseAtPositionOfKind(document.FullName, pos, SymbolKind.Ident)
let! patMatchExpr, insertionParams = tryFindCaseInsertionParamsAtPos codeGenService pos document


let! superficialTypeDefinition =
asyncOption {
let! symbolUse = symbolUse

match symbolUse.Symbol with
| SymbolPatterns.MemberFunctionOrValue(mfv) -> return Some mfv.FullType.TypeDefinition
| SymbolPatterns.UnionCase(case) when case.ReturnType.HasTypeDefinition ->
return Some case.ReturnType.TypeDefinition
| SymbolPatterns.FSharpEntity(entity, _, _) -> return Some entity
Expand Down
43 changes: 34 additions & 9 deletions src/FsAutoComplete/CodeFixes/GenerateUnionCases.fs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,47 @@ let fix
(getTextReplacements: unit -> Map<string, string>)
=
Run.ifDiagnosticByCode (Set.ofList [ "25" ]) (fun diagnostic codeActionParams ->
let getCasePosFromCaseLine (lines: IFSACSourceText) (fcsRange: FcsRange) =
result {
let! nextLine = lines.NextLine fcsRange.Start |> Result.ofOption (fun _ -> "no next line")

let! caseLine = lines.GetLine(nextLine) |> Result.ofOption (fun _ -> "No case line")

let! caseCol =
match caseLine.IndexOf('|') with
| -1 -> Error "Invalid case line"
| idx -> Ok(uint32 idx + 3u) // Find column of first case in pattern matching

let casePos =
{ Line = uint32 nextLine.Line - 1u
Character = caseCol }

return casePos
}

let getCasePosFromMatch (lines: IFSACSourceText) (fcsRange: FcsRange) =
result {
let! matchLine = lines.GetLine fcsRange.Start |> Result.ofOption (fun _ -> "no current line")
let caseCol = matchLine.IndexOf("match")

let casePos =
{ Line = uint32 fcsRange.Start.Line - 1u
Character = uint32 caseCol + 7u }

return casePos
}

asyncResult {
let fileName = codeActionParams.TextDocument.GetFilePath() |> Utils.normalizePath
let fileName = codeActionParams.TextDocument.GetFilePath() |> normalizePath

let! lines = getFileLines fileName
// try to find the first case already written
let fcsRange = protocolRangeToRange (FSharp.UMX.UMX.untag fileName) diagnostic.Range

let! nextLine = lines.NextLine fcsRange.Start |> Result.ofOption (fun _ -> "no next line")

let! caseLine = lines.GetLine(nextLine) |> Result.ofOption (fun _ -> "No case line")

let caseCol = uint32 (caseLine.IndexOf('|')) + 3u // Find column of first case in pattern matching

let casePos =
{ Line = uint32 nextLine.Line - 1u
Character = caseCol }
let! casePos =
(getCasePosFromCaseLine lines fcsRange)
|> Result.orElseWith (fun _ -> getCasePosFromMatch lines fcsRange)

let casePosFCS = protocolPosToPos casePos

Expand Down
34 changes: 29 additions & 5 deletions test/FsAutoComplete.Tests.Lsp/CodeFixTests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2100,7 +2100,7 @@ let private generateUnionCasesTests state =
serverTestList (nameof GenerateUnionCases) state config None (fun server ->
[ let _selectCodeFix = CodeFix.withTitle GenerateUnionCases.title

testCaseAsync "can generate match cases for a simple DU"
testCaseAsync "can generate match cases for a simple DU with one case"
<| CodeFix.check
server
"""
Expand All @@ -2122,6 +2122,29 @@ let private generateUnionCasesTests state =
| A -> ()
| B -> failwith "---"
| C -> failwith "---"
"""

testCaseAsync "can generate match cases for a simple DU without cases"
<| CodeFix.check
server
"""
type Letter = A | B | C

let char = A

match $0char with
"""
(Diagnostics.expectCode "25")
(CodeFix.withTitle GenerateUnionCases.title)
"""
type Letter = A | B | C

let char = A

match char with
| A -> failwith "---"
| B -> failwith "---"
| C -> failwith "---"
""" ])

let private makeDeclarationMutableTests state =
Expand Down Expand Up @@ -2724,6 +2747,7 @@ let private replaceWithSuggestionTests state =
let validateDiags (diags: Diagnostic[]) =
Diagnostics.expectCode "39" diags
let messages = diags |> Array.map (fun d -> d.Message) |> String.concat "\n"

Expect.exists
diags
(fun (d: Diagnostic) -> d.Message.Contains "Maybe you want one of the following:")
Expand Down Expand Up @@ -3175,8 +3199,8 @@ let private removePatternArgumentTests state =
| B = 2

do
let (E.A x$0) = E.A
()
let (E.A x$0) = E.A
()
"""
(Diagnostics.expectCode "3191")
selectCodeFix
Expand All @@ -3186,8 +3210,8 @@ let private removePatternArgumentTests state =
| B = 2

do
let (E.A) = E.A
()
let (E.A) = E.A
()
"""

testCaseAsync "Local literal constant pattern qualified parameter"
Expand Down
Loading