Skip to content

Commit 102cde3

Browse files
Handle partially qualified dotted-name completion
When completing an invisible item with a partially qualified prefix (e.g: 'P2' instead of 'P1.P2.'), we should not try to rely on the prefix being typed by the user: we should just import the fully qualified unit. Add a test for this. For eng/ide/ada_language_server#1610
1 parent 95a5893 commit 102cde3

File tree

9 files changed

+111
-35
lines changed

9 files changed

+111
-35
lines changed

source/ada/lsp-ada_documents.adb

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -189,47 +189,35 @@ package body LSP.Ada_Documents is
189189
-- "Ada.Text_IO.Put_Line" subprogram).
190190

191191
begin
192-
Missing_Unit_Name := VSS.Strings.Conversions.To_Virtual_String
193-
(Langkit_Support.Text.To_UTF8
194-
(Missing_Unit_Root_Decl.P_Fully_Qualified_Name));
192+
Missing_Unit_Name :=
193+
VSS.Strings.Conversions.To_Virtual_String
194+
(Langkit_Support.Text.To_UTF8
195+
(Missing_Unit_Root_Decl.P_Fully_Qualified_Name));
195196

196197
-- We are completing a dotted name but its prefix does not match
197198
-- with the completion item's defining name's unit: this means we
198-
-- are dealing with renames (e.g: 'GNAT.Strings.Strings_Access'
199-
-- is a forward declaration of 'System.Strings.String_Access'). In
200-
-- that case, use the prefix specified by the user instead of the
201-
-- completion item's defining name's unit: the user explcitly wants
202-
-- to use the renamed symbol instead of the base one.
203-
199+
-- are dealing with either renames (e.g: 'GNAT.Strings.Strings_Access'
200+
-- is a forward declaration of 'System.Strings.String_Access') or
201+
-- partially qualified dotted completion (e.g: the user typed
202+
-- 'Child.' to reference a 'Parent.Child.' package).
203+
-- If the prefix corresponds to a known LAL unit (will be the case for
204+
-- renames), import the unit corresponding to the prefix instead.
205+
-- We don't want to this for partially qualified names: we still want
206+
-- to import the fully qualified unit (so import 'Parent.Child' even
207+
-- if the user just type 'Child.')
204208
if Is_Dotted_Name
205209
and then not Missing_Unit_Name.Starts_With (Dotted_Node_Prefix)
210+
and then not Get_From_Provider
211+
(Context => Context.LAL_Context,
212+
Name =>
213+
Langkit_Support.Text.To_Text
214+
(VSS.Strings.Conversions.To_UTF_8_String
215+
(Dotted_Node_Prefix)),
216+
Kind => Libadalang.Common.Unit_Specification)
217+
.Root
218+
.Is_Null
206219
then
207-
declare
208-
Dotted_Prefix_Parts : VSS.String_Vectors.
209-
Virtual_String_Vector :=
210-
Dotted_Node_Prefix.Split
211-
(Separator => VSS.Characters.Latin.Full_Stop);
212-
begin
213-
-- Check if the unit specified as a prefix actually exists.
214-
-- If not, it might be a renamed package
215-
-- declaration/instantiation: in that case we want to add a
216-
-- with-clause on the enclosing unit (e.g: the prefix before
217-
-- the last '.').
218-
219-
while Get_From_Provider
220-
(Context => Context.LAL_Context,
221-
Name => Langkit_Support.Text.To_Text
222-
(VSS.Strings.Conversions.To_UTF_8_String
223-
(Dotted_Node_Prefix)),
224-
Kind => Libadalang.Common.Unit_Specification).Root.Is_Null
225-
loop
226-
Dotted_Prefix_Parts.Delete_Last;
227-
Dotted_Node_Prefix :=
228-
Dotted_Prefix_Parts.Join (VSS.Characters.Latin.Full_Stop);
229-
end loop;
230-
231-
Missing_Unit_Name := Dotted_Node_Prefix;
232-
end;
220+
Missing_Unit_Name := Dotted_Node_Prefix;
233221
end if;
234222

235223
-- We should not add any qualifier if the user accepted the
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
project Default is
2+
end Default;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function P1.P2.Subp return Integer is
2+
begin
3+
return 0;
4+
end P1.P2.Subp;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
function P1.P2.Subp return Integer;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package P1.P2 is
2+
type T is null record;
3+
end P1.P2;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package P1.P3 is
2+
Obj : Integer := P2.
3+
end P1.P3;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package P1 is
2+
pragma Pure;
3+
end P1;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from typing import List
2+
from drivers import pylsp
3+
from lsprotocol.types import (
4+
Command,
5+
CompletionContext,
6+
CompletionItem,
7+
CompletionList,
8+
CompletionParams,
9+
CompletionTriggerKind,
10+
TextDocumentIdentifier,
11+
)
12+
13+
14+
@pylsp.test()
15+
async def test(lsp: pylsp.ALSLanguageClient) -> None:
16+
file_uri = lsp.didOpenFile("p1-p3.ads")
17+
18+
await lsp.awaitIndexingEnd()
19+
20+
# Complete 'P1.' in 'P1.P3' package spec. This should list
21+
# the top-level subprogram unit 'P1.P2.Subp' as an invisible
22+
# completion item.
23+
24+
result = await lsp.text_document_completion_async(
25+
CompletionParams(
26+
TextDocumentIdentifier(file_uri),
27+
pylsp.Pos(2, 24),
28+
CompletionContext(CompletionTriggerKind.Invoked),
29+
)
30+
)
31+
EXPECTED_LABEL = "Subp (invisible)"
32+
EXPECTED_COMMAND = Command(
33+
title="",
34+
command="als-auto-import",
35+
arguments=[
36+
{
37+
"context": pylsp.URI("default.gpr"),
38+
"where": {
39+
"textDocument": {"uri": file_uri},
40+
"position": {"line": 1, "character": 23},
41+
},
42+
"import": "P1.P2.Subp",
43+
"qualifier": "",
44+
}
45+
],
46+
)
47+
48+
def find_expected_completion_item(
49+
items: List[CompletionItem],
50+
expected_label,
51+
expected_command,
52+
):
53+
"""
54+
Find the expected completion item in the given completion items' list.
55+
"""
56+
expected_items = [
57+
item
58+
for item in items
59+
if item.label == expected_label and item.command == expected_command
60+
]
61+
return len(expected_items) == 1
62+
63+
# Verify that we are able to find the expected completion invisible item
64+
# for 'P1.P2.Subp'
65+
assert isinstance(
66+
result, CompletionList
67+
), "The returned completion list result should be of type CompletionList"
68+
lsp.assertEqual(
69+
find_expected_completion_item(result.items, EXPECTED_LABEL, EXPECTED_COMMAND),
70+
True,
71+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
driver: pylsp

0 commit comments

Comments
 (0)