Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/10.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
* Caches: type subsumption cache key perf regression ([Issue #18925](https://github.com/dotnet/fsharp/issues/18925) [PR #18926](https://github.com/dotnet/fsharp/pull/18926))
* Ensure that line directives are applied to source identifiers (issue [#18908](https://github.com/dotnet/fsharp/issues/18908), PR [#18918](https://github.com/dotnet/fsharp/pull/18918))
* Fix expected and actual types in ErrorFromAddingTypeEquation message and extended diagnostic data. ([PR #18915](https://github.com/dotnet/fsharp/pull/18915))
* Editor: Fix Record fields completion in update record with partial field name. ([PR #18946](https://github.com/dotnet/fsharp/pull/18946))

### Changed
* Use `errorR` instead of `error` in `CheckDeclarations.fs` when possible. ([PR #18645](https://github.com/dotnet/fsharp/pull/18645))
Expand Down
12 changes: 11 additions & 1 deletion src/Compiler/Service/ServiceParseTreeWalk.fs
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,17 @@ module SyntaxTraversal =
for SynExprRecordField(fieldName = (field, _); expr = e; blockSeparator = sepOpt) in fields do
yield
dive (path, copyOpt, Some field) field.Range (fun r ->
if rangeContainsPos field.Range pos then
// Treat the caret placed right after the field name (before '=' or a value) as "inside" the field,
// but only if the field does not yet have a value.
//
// Examples (the '$' marks the caret):
// { r with Field1$ }
// { r with
// Field1$
// }
let isCaretAfterFieldNameWithoutValue = (e.IsNone && posEq pos field.Range.End)

if rangeContainsPos field.Range pos || isCaretAfterFieldNameWithoutValue then
visitor.VisitRecordField r
else
None)
Expand Down
78 changes: 58 additions & 20 deletions tests/FSharp.Compiler.Service.Tests/EditorTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1937,6 +1937,15 @@ let hasRecordType (recordTypeName: string) (symbolUses: FSharpSymbolUse list) =
| _ -> false
)
|> fun exists -> Assert.True(exists, $"Record type {recordTypeName} not found.")

let private assertItemsWithNames contains names (completionInfo: DeclarationListInfo) =
let itemNames = completionInfo.Items |> Array.map _.NameInCode |> set

for name in names do
Assert.True(Set.contains name itemNames = contains)

let assertHasItemWithNames names (completionInfo: DeclarationListInfo) =
assertItemsWithNames true names completionInfo

[<Fact>]
let ``Record fields are completed via type name usage`` () =
Expand Down Expand Up @@ -2113,34 +2122,63 @@ let rUpdate = { r1 with }
hasRecordField "Field1" declarations
hasRecordField "Field2" declarations

[<Fact(Skip = "Current fails to suggest any record fields")>]
[<Fact>]
let ``Record fields are completed in update record with partial field name`` () =
let parseResults, checkResults =
getParseAndCheckResults """
let info = Checker.getCompletionInfo """
module Module

type R1 =
{ Field1: int; Field2: int }

let r1 = { Field1 = 1; Field2 = 2 }

let rUpdate = { r1 with Fi }
let rUpdate = { r1 with Fi{caret} }
"""

let declarations =
checkResults.GetDeclarationListSymbols(
Some parseResults,
9,
"let rUpdate = { r1 with Fi }",
{
EndColumn = 26
LastDotPos = None
PartialIdent = "Fi"
QualifyingIdents = []
},
fun _ -> List.empty
)
|> List.concat
assertHasItemWithNames ["Field1"; "Field2"] info

hasRecordField "Field1" declarations
hasRecordField "Field2" declarations
[<Fact>]
let ``Multiline record fields are completed in update record with partial field name`` () =
let info = Checker.getCompletionInfo """
module Module

type R1 =
{ Field1: int; Field2: int }

let r1 = { Field1 = 1; Field2 = 2 }

let rUpdate =
{ r1 with
Fi{caret}
}
"""

assertHasItemWithNames ["Field1"; "Field2"] info

[<Fact>]
let ``No record field completion after '=' with missing value in first binding (after =;)`` () =
let info = Checker.getCompletionInfo """
module M

type X =
val field1: int
val field2: string

let x = new X()
let _ = { field1 =; {caret} }
"""
assertItemsWithNames false ["field1"; "field2"] info

[<Fact>]
let ``No record field completion after '=' with missing value in first binding (after =; f)`` () =
let info = Checker.getCompletionInfo """
module M

type X =
val field1: int
val field2: string

let x = new X()
let _ = { field1 =; f{caret} }
"""
assertItemsWithNames false ["field1"; "field2"] info
Loading