From cec994c29805fd03afbd3b5d8a11e29beb0b9a22 Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Tue, 29 Apr 2025 20:31:38 +0200 Subject: [PATCH 1/3] Completion: fix getting qualifier expr in do statements in type decls --- src/Compiler/Service/ServiceParseTreeWalk.fs | 3 + tests/FSharp.Compiler.Service.Tests/Common.fs | 10 ++ .../CompletionTests.fs | 113 ++++++++++++------ 3 files changed, 92 insertions(+), 34 deletions(-) diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fs b/src/Compiler/Service/ServiceParseTreeWalk.fs index 659f4c4bc60..d75bb6d0db1 100644 --- a/src/Compiler/Service/ServiceParseTreeWalk.fs +++ b/src/Compiler/Service/ServiceParseTreeWalk.fs @@ -1014,6 +1014,9 @@ module SyntaxTraversal = let path = SyntaxNode.SynBinding b :: origPath match b with + | SynBinding(kind = SynBindingKind.Do; expr = expr) -> + traverseSynExpr path expr + | SynBinding(headPat = synPat; expr = synExpr; attributes = attributes; range = m) -> [ yield! attributeApplicationDives path attributes diff --git a/tests/FSharp.Compiler.Service.Tests/Common.fs b/tests/FSharp.Compiler.Service.Tests/Common.fs index dad4f489514..f1de5739d47 100644 --- a/tests/FSharp.Compiler.Service.Tests/Common.fs +++ b/tests/FSharp.Compiler.Service.Tests/Common.fs @@ -342,6 +342,16 @@ let rec allSymbolsInEntities compGen (entities: IList) = yield! allSymbolsInEntities compGen entity.NestedEntities ] +let getCursorPosAndPrepareSource (source: string) : string * string * pos = + let lines = source.Split([|"\r\n"; "\n"|], StringSplitOptions.None) + let line = lines |> Seq.findIndex _.Contains("{caret}") + let lineText = lines[line] + let column = lineText.IndexOf("{caret}") + + let source = source.Replace("{caret}", "") + let lineText = lineText.Replace("{caret}", "") + source, lineText, Position.mkPos (line + 1) (column - 1) + let getParseResults (source: string) = parseSourceCode("Test.fsx", source) diff --git a/tests/FSharp.Compiler.Service.Tests/CompletionTests.fs b/tests/FSharp.Compiler.Service.Tests/CompletionTests.fs index c4a924d25d9..2d70f5c58f6 100644 --- a/tests/FSharp.Compiler.Service.Tests/CompletionTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/CompletionTests.fs @@ -4,10 +4,11 @@ open FSharp.Compiler.Service.Tests.Common open FSharp.Compiler.EditorServices open Xunit -let getCompletionInfo lineText (line, column) source = +let getCompletionInfo source = + let source, lineText, pos = getCursorPosAndPrepareSource source let parseResults, checkResults = getParseAndCheckResultsPreview source - let plid = QuickParse.GetPartialLongNameEx(lineText, column) - checkResults.GetDeclarationListInfo(Some parseResults, line, lineText, plid) + let plid = QuickParse.GetPartialLongNameEx(lineText, pos.Column) + checkResults.GetDeclarationListInfo(Some parseResults, pos.Line, lineText, plid) let getCompletionItemNames (completionInfo: DeclarationListInfo) = completionInfo.Items |> Array.map (fun item -> item.NameInCode) @@ -19,92 +20,100 @@ let assertHasItemWithNames names (completionInfo: DeclarationListInfo) = Assert.True(Set.contains name itemNames, $"{name} not found in {itemNames}") [] -let ``Expr - After record decl`` () = - let info = getCompletionInfo "{ Fi }" (4, 0) """ +let ``Expr - After record decl 01`` () = + let info = getCompletionInfo """ type Record = { Field: int } +{ Fi{caret} } +""" + assertHasItemWithNames ["ignore"] info +[] +let ``Expr - After record decl 02`` () = + let info = getCompletionInfo """ +type Record = { Field: int } + +{caret} """ assertHasItemWithNames ["ignore"] info [] let ``Expr - record - field 01 - anon module`` () = - let info = getCompletionInfo "{ Fi }" (4, 3) """ + let info = getCompletionInfo """ type Record = { Field: int } -{ Fi } +{ Fi{caret} } """ assertHasItemWithNames ["Field"] info [] let ``Expr - record - field 02 - anon module`` () = - let info = getCompletionInfo "{ Fi }" (6, 3) """ + let info = getCompletionInfo """ type Record = { Field: int } let record = { Field = 1 } -{ Fi } +{ Fi{caret} } """ assertHasItemWithNames ["Field"] info [] let ``Expr - record - empty 01`` () = - let info = getCompletionInfo "{ }" (4, 2) """ + let info = getCompletionInfo """ type Record = { Field: int } -{ } +{ {caret} } """ assertHasItemWithNames ["Field"] info [] let ``Expr - record - empty 02`` () = - let info = getCompletionInfo "{ }" (6, 2) """ + let info = getCompletionInfo """ type Record = { Field: int } let record = { Field = 1 } -{ } +{ {caret} } """ assertHasItemWithNames ["Field"; "record"] info [] let ``Underscore dot lambda - completion`` () = - let info = getCompletionInfo " |> _.Len" (4, 11) """ + let info = getCompletionInfo """ let myFancyFunc (x:string) = x - |> _.Len""" + |> _.Len{caret}""" assertHasItemWithNames ["Length"] info [] let ``Underscore dot lambda - method completion`` () = - let info = getCompletionInfo " |> _.ToL" (4, 11) """ + let info = getCompletionInfo """ let myFancyFunc (x:string) = x - |> _.ToL""" + |> _.ToL{caret}""" assertHasItemWithNames ["ToLower"] info [] let ``Underscore dot lambda - No prefix`` () = - let info = getCompletionInfo "[s] |> List.map _. " (3, 18) """ + let info = getCompletionInfo """ let s = "" -[s] |> List.map _. +[s] |> List.map _.{caret} """ assertHasItemWithNames ["Length"] info [] let ``Type decl - Record - Field type 01`` () = - let info = getCompletionInfo "type Record = { Field: }" (2, 23) """ -type Record = { Field: } + let info = getCompletionInfo """ +type Record = { Field: {caret} } """ assertHasItemWithNames ["string"] info [] let ``Expr - Qualifier 01`` () = - let info = - getCompletionInfo "s.Trim(). " (3, 13) """ + let info = getCompletionInfo """ let f (s: string) = - s.Trim(). + s.Trim().{caret} s.Trim() s.Trim() () @@ -114,10 +123,10 @@ let f (s: string) = [] let ``Expr - Qualifier 02`` () = let info = - getCompletionInfo "s.Trim(). " (4, 13) """ + getCompletionInfo """ let f (s: string) = s.Trim() - s.Trim(). + s.Trim().{caret} s.Trim() () """ @@ -126,20 +135,56 @@ let f (s: string) = [] let ``Expr - Qualifier 03`` () = let info = - getCompletionInfo "s.Trim(). " (5, 13) """ + getCompletionInfo """ let f (s: string) = s.Trim() s.Trim() - s.Trim(). + s.Trim().{caret} () """ assertHasItemWithNames ["Length"] info +[] +let ``Expr - Qualifier 04`` () = + let info = + getCompletionInfo """ +type T() = + do + System.String.Empty.ToString().L{caret} +""" + assertHasItemWithNames ["Length"] info + +[] +let ``Expr - Qualifier 05`` () = + let info = + getCompletionInfo """ +System.String.Empty.ToString().{caret} +""" + assertHasItemWithNames ["Length"] info + +[] +let ``Expr - Qualifier 06`` () = + let info = + getCompletionInfo """ +System.String.Empty.ToString().L{caret} +""" + assertHasItemWithNames ["Length"] info + +[] +let ``Expr - Qualifier 07`` () = + let info = + getCompletionInfo """ +type T() = + do + System.String.Empty.ToString().L{caret} + () +""" + assertHasItemWithNames ["Length"] info [] let ``Import - Ns 01`` () = let info = - getCompletionInfo "let _: R " (14, 12) """ + getCompletionInfo """ namespace Ns type Rec1 = { F: int } @@ -153,14 +198,14 @@ module M = type Rec3 = { F: int } - let _: R = () + let _: R{caret} = () """ assertHasItemWithNames ["Rec1"; "Rec2"; "Rec3"] info [] let ``Import - Ns 02 - Rec`` () = let info = - getCompletionInfo "let _: R " (14, 12) """ + getCompletionInfo """ namespace Ns type Rec1 = { F: int } @@ -174,14 +219,14 @@ module M = type Rec3 = { F: int } - let _: R = () + let _: R{caret} = () """ assertHasItemWithNames ["Rec1"; "Rec2"; "Rec3"] info [] let ``Import - Ns 03 - Rec`` () = let info = - getCompletionInfo "let _: R " (14, 12) """ + getCompletionInfo """ namespace Ns type Rec1 = { F: int } @@ -195,6 +240,6 @@ module rec M = type Rec3 = { F: int } - let _: R = () + let _: R{caret} = () """ assertHasItemWithNames ["Rec1"; "Rec2"; "Rec3"] info \ No newline at end of file From f672b172e3d1c4af19bbe3abc2bbd6d251a1674a Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Tue, 29 Apr 2025 20:46:23 +0200 Subject: [PATCH 2/3] Fantomas --- src/Compiler/Service/ServiceParseTreeWalk.fs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fs b/src/Compiler/Service/ServiceParseTreeWalk.fs index d75bb6d0db1..33734db9a62 100644 --- a/src/Compiler/Service/ServiceParseTreeWalk.fs +++ b/src/Compiler/Service/ServiceParseTreeWalk.fs @@ -1014,8 +1014,7 @@ module SyntaxTraversal = let path = SyntaxNode.SynBinding b :: origPath match b with - | SynBinding(kind = SynBindingKind.Do; expr = expr) -> - traverseSynExpr path expr + | SynBinding(kind = SynBindingKind.Do; expr = expr) -> traverseSynExpr path expr | SynBinding(headPat = synPat; expr = synExpr; attributes = attributes; range = m) -> [ From c43a6e69e7ff6975ee97091e98c6cec332374646 Mon Sep 17 00:00:00 2001 From: Eugene Auduchinok Date: Tue, 29 Apr 2025 20:47:27 +0200 Subject: [PATCH 3/3] Release notes --- docs/release-notes/.FSharp.Compiler.Service/9.0.300.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md index 8b539ee2e56..7cb7eae4d6c 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/9.0.300.md @@ -28,6 +28,7 @@ * Allow `_` in `use!` bindings values (lift FS1228 restriction) ([PR #18487](https://github.com/dotnet/fsharp/pull/18487)) * Make `[]` combination work([PR #18444](https://github.com/dotnet/fsharp/pull/18444/)) * Fix code completion considers types from own namespace non-imported ([PR #18518](https://github.com/dotnet/fsharp/issues/18518)) +* Code completion: fix getting qualifier expression in do statements in type decls ([PR #18524](https://github.com/dotnet/fsharp/pull/18524)) ### Added * Added missing type constraints in FCS. ([PR #18241](https://github.com/dotnet/fsharp/pull/18241))