From 4f0623a60b96a61962a8f05de94cbdf94edfb11e Mon Sep 17 00:00:00 2001 From: Edgar Gonzalez Date: Mon, 29 Sep 2025 16:15:03 +0100 Subject: [PATCH 1/4] Failing test --- tests/FSharp.Compiler.Service.Tests/EditorTests.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FSharp.Compiler.Service.Tests/EditorTests.fs b/tests/FSharp.Compiler.Service.Tests/EditorTests.fs index 6df9a1dc4a9..a98a100c8cb 100644 --- a/tests/FSharp.Compiler.Service.Tests/EditorTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/EditorTests.fs @@ -2113,7 +2113,7 @@ let rUpdate = { r1 with } hasRecordField "Field1" declarations hasRecordField "Field2" declarations -[] +[] let ``Record fields are completed in update record with partial field name`` () = let parseResults, checkResults = getParseAndCheckResults """ From f41ef6d5774e1bc6ba1ea40aaa0324fe5e8abdcf Mon Sep 17 00:00:00 2001 From: Edgar Gonzalez Date: Mon, 29 Sep 2025 16:51:09 +0100 Subject: [PATCH 2/4] Fix --- src/Compiler/Service/ServiceParseTreeWalk.fs | 12 +++++- .../EditorTests.fs | 38 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fs b/src/Compiler/Service/ServiceParseTreeWalk.fs index 133df625a8a..cb4915902a8 100644 --- a/src/Compiler/Service/ServiceParseTreeWalk.fs +++ b/src/Compiler/Service/ServiceParseTreeWalk.fs @@ -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 && posGeq pos field.Range.End) + + if rangeContainsPos field.Range pos || isCaretAfterFieldNameWithoutValue then visitor.VisitRecordField r else None) diff --git a/tests/FSharp.Compiler.Service.Tests/EditorTests.fs b/tests/FSharp.Compiler.Service.Tests/EditorTests.fs index a98a100c8cb..b94e777598e 100644 --- a/tests/FSharp.Compiler.Service.Tests/EditorTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/EditorTests.fs @@ -2144,3 +2144,41 @@ let rUpdate = { r1 with Fi } hasRecordField "Field1" declarations hasRecordField "Field2" declarations + +[] +let ``Multiline record fields are completed in update record with partial field name`` () = + let parseResults, checkResults = + getParseAndCheckResults """ +module Module + +type R1 = + { Field1: int; Field2: int } + +let r1 = { Field1 = 1; Field2 = 2 } + +let rUpdate2 = + { r1 with + Fi + } +""" + + let declarations = + checkResults.GetDeclarationListSymbols( + Some parseResults, + 10, + "let rUpdate2 = + { r1 with + Fi + }", + { + EndColumn = 14 + LastDotPos = None + PartialIdent = "Fi" + QualifyingIdents = [] + }, + fun _ -> List.empty + ) + |> List.concat + + hasRecordField "Field1" declarations + hasRecordField "Field2" declarations From 5f0ff48664c0cd63711707d5cb57b54a29845ad5 Mon Sep 17 00:00:00 2001 From: Edgar Gonzalez Date: Mon, 29 Sep 2025 20:28:43 +0100 Subject: [PATCH 3/4] Use code completion context --- src/Compiler/Service/ServiceParseTreeWalk.fs | 2 +- .../EditorTests.fs | 86 +++++++++---------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fs b/src/Compiler/Service/ServiceParseTreeWalk.fs index cb4915902a8..f280df1b237 100644 --- a/src/Compiler/Service/ServiceParseTreeWalk.fs +++ b/src/Compiler/Service/ServiceParseTreeWalk.fs @@ -536,7 +536,7 @@ module SyntaxTraversal = // { r with // Field1$ // } - let isCaretAfterFieldNameWithoutValue = (e.IsNone && posGeq pos field.Range.End) + let isCaretAfterFieldNameWithoutValue = (e.IsNone && posEq pos field.Range.End) if rangeContainsPos field.Range pos || isCaretAfterFieldNameWithoutValue then visitor.VisitRecordField r diff --git a/tests/FSharp.Compiler.Service.Tests/EditorTests.fs b/tests/FSharp.Compiler.Service.Tests/EditorTests.fs index b94e777598e..6ce135326fc 100644 --- a/tests/FSharp.Compiler.Service.Tests/EditorTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/EditorTests.fs @@ -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 [] let ``Record fields are completed via type name usage`` () = @@ -2115,8 +2124,7 @@ let rUpdate = { r1 with } [] let ``Record fields are completed in update record with partial field name`` () = - let parseResults, checkResults = - getParseAndCheckResults """ + let info = Checker.getCompletionInfo """ module Module type R1 = @@ -2124,31 +2132,14 @@ type R1 = 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 - - hasRecordField "Field1" declarations - hasRecordField "Field2" declarations + assertHasItemWithNames ["Field1"; "Field2"] info [] let ``Multiline record fields are completed in update record with partial field name`` () = - let parseResults, checkResults = - getParseAndCheckResults """ + let info = Checker.getCompletionInfo """ module Module type R1 = @@ -2156,29 +2147,38 @@ type R1 = let r1 = { Field1 = 1; Field2 = 2 } -let rUpdate2 = +let rUpdate = { r1 with - Fi + Fi{caret} } """ - let declarations = - checkResults.GetDeclarationListSymbols( - Some parseResults, - 10, - "let rUpdate2 = - { r1 with - Fi - }", - { - EndColumn = 14 - LastDotPos = None - PartialIdent = "Fi" - QualifyingIdents = [] - }, - fun _ -> List.empty - ) - |> List.concat + assertHasItemWithNames ["Field1"; "Field2"] info + +[] +let ``No record field completion after '=' with missing value in first binding (after =;)`` () = + let info = Checker.getCompletionInfo """ +module M - hasRecordField "Field1" declarations - hasRecordField "Field2" declarations +type X = + val field1: int + val field2: string + +let x = new X() +let _ = { field1 =; {caret} } +""" + assertItemsWithNames false ["field1"; "field2"] info + +[] +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 \ No newline at end of file From f78abe1bf0be3cac495701c0e3e0e43b95201708 Mon Sep 17 00:00:00 2001 From: Edgar Gonzalez Date: Mon, 29 Sep 2025 21:54:47 +0100 Subject: [PATCH 4/4] release notes --- docs/release-notes/.FSharp.Compiler.Service/10.0.100.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md index fe1ddaf787d..89afb46101b 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md @@ -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))