Skip to content

Commit 512a3a2

Browse files
committed
Test: use AI to summarise call sites
Extends command "Find All References".
1 parent 9c069b7 commit 512a3a2

12 files changed

+518
-54
lines changed

analysis/src/Commands.ml

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -140,30 +140,51 @@ let typeDefinition ~path ~pos ~debug =
140140
| Some location -> location |> Protocol.stringifyLocation)
141141

142142
let references ~path ~pos ~debug =
143-
let allLocs =
143+
let allLocs, prompt_segments =
144144
match Cmt.loadFullCmtFromPath ~path with
145-
| None -> []
145+
| None -> ([], [])
146146
| Some full -> (
147147
match References.getLocItem ~full ~pos ~debug with
148-
| None -> []
148+
| None -> ([], [])
149149
| Some locItem ->
150+
let itemName =
151+
match locItem.locType with
152+
| Typed (name, _, _) -> name
153+
| _ -> "UnknownName"
154+
in
155+
let prompt = Prompt.createForReferences itemName in
150156
let allReferences = References.allReferencesForLocItem ~full locItem in
151-
allReferences
152-
|> List.fold_left
153-
(fun acc {References.uri = uri2; locOpt} ->
154-
let loc =
155-
match locOpt with
156-
| Some loc -> loc
157-
| None -> Uri.toTopLevelLoc uri2
158-
in
159-
Protocol.stringifyLocation
160-
{uri = Uri.toString uri2; range = Utils.cmtLocToRange loc}
161-
:: acc)
162-
[])
157+
let references =
158+
allReferences
159+
|> List.fold_left
160+
(fun acc {References.uri = uri2; locOpt} ->
161+
let loc =
162+
match locOpt with
163+
| Some loc -> loc
164+
| None -> Uri.toTopLevelLoc uri2
165+
in
166+
prompt
167+
|> Prompt.addSnippet ~isDefinition:(locItem.loc = loc)
168+
~pos:(Loc.start loc) ~uri:uri2;
169+
Protocol.stringifyLocation
170+
{uri = Uri.toString uri2; range = Utils.cmtLocToRange loc}
171+
:: acc)
172+
[]
173+
in
174+
(references, Prompt.toSegments prompt))
163175
in
176+
164177
print_endline
165178
(if allLocs = [] then Protocol.null
166-
else "[\n" ^ (allLocs |> String.concat ",\n") ^ "\n]")
179+
else
180+
let prompt =
181+
prompt_segments
182+
|> List.map Protocol.wrapInQuotes
183+
|> Protocol.array_newline
184+
in
185+
let references = allLocs |> Protocol.array_newline in
186+
Printf.sprintf "{\"prompt\":\n%s,\n\"references\":\n%s}\n" prompt
187+
references)
167188

168189
let rename ~path ~pos ~newName ~debug =
169190
let result =

analysis/src/Prompt.ml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
type lines = string array
2+
type snippet = {uri: Uri.t; pos: Pos.t; code: string}
3+
type t = {
4+
mutable definition: snippet option;
5+
mutable files: (string, lines) Hashtbl.t;
6+
mutable preamble: string;
7+
mutable snippets: snippet list;
8+
}
9+
10+
let readFile {files} name =
11+
match Hashtbl.find_opt files name with
12+
| None -> (
13+
match Files.readFile name with
14+
| None -> None
15+
| Some text -> Some (text |> String.split_on_char '\n' |> Array.of_list))
16+
| Some lines -> Some lines
17+
18+
let snippetRadius (lines : lines) = min (Array.length lines) 10 / 2
19+
20+
let addSnippet ~isDefinition ~pos ~(uri : Uri.t) prompt =
21+
match readFile prompt (Uri.toPath uri) with
22+
| None -> () (* ignore files not found *)
23+
| Some lines ->
24+
let lineNum = fst pos in
25+
let radius = snippetRadius lines in
26+
let firstLine = max 0 (lineNum - radius) in
27+
let lastLine = min (Array.length lines - 1) (lineNum + radius) in
28+
let linesInRadius = Array.sub lines firstLine (lastLine - firstLine) in
29+
let code = linesInRadius |> Array.to_list |> String.concat "\n" in
30+
let snippet = {uri; pos; code} in
31+
if isDefinition then prompt.definition <- Some snippet
32+
else prompt.snippets <- snippet :: prompt.snippets
33+
34+
let printSnippet buf {uri; pos; code} =
35+
Buffer.add_string buf
36+
("{\"file\": "
37+
^ Filename.basename (Uri.toString uri)
38+
^ ", \"line\": "
39+
^ string_of_int (1 + fst pos)
40+
^ ", \"code\":\n");
41+
Buffer.add_string buf code;
42+
Buffer.add_string buf "\"}\n"
43+
44+
let createForReferences name =
45+
let quoted = "\"" ^ name ^ "\"" in
46+
let backticked = "`" ^ name ^ "`" in
47+
let preamble =
48+
[
49+
{|A Snippet has the form: {"file": "Hello.res", "line":23, "code": "...the code..."} where the first line of code is line 23.|};
50+
"Find Uses of " ^ quoted
51+
^ " given snippets for its Definition and its Users.";
52+
"";
53+
{|The input has form:
54+
Definition:
55+
snippet
56+
57+
Use1:
58+
snippet1
59+
60+
Use2:
61+
snippet2
62+
...|};
63+
"";
64+
"You will produce output of the form:";
65+
"- `File.res` line `12`: function `foo` calls " ^ backticked ^ " ...";
66+
"- `File2.res` line `34`: function `bar` uses " ^ backticked ^ " to ...";
67+
"";
68+
"Ignore any code in the Snippets that is not directly relevant to using "
69+
^ quoted ^ ".";
70+
"Add enough details to understand at high level how " ^ quoted
71+
^ " is used targeted at a person who is trying to undersand the codebase.";
72+
]
73+
|> String.concat "\n"
74+
in
75+
{definition = None; files = Hashtbl.create 1; preamble; snippets = []}
76+
77+
let toSegments {definition; preamble; snippets} =
78+
let segments = ref [] in
79+
let addSegment s = segments := s :: !segments in
80+
addSegment (preamble ^ "\n");
81+
(match definition with
82+
| None -> ()
83+
| Some snippet ->
84+
let buf = Buffer.create 1 in
85+
Buffer.add_string buf "Definition:\n";
86+
printSnippet buf snippet;
87+
addSegment (Buffer.contents buf));
88+
snippets
89+
|> List.iteri (fun i s ->
90+
let buf = Buffer.create 1 in
91+
Buffer.add_string buf ("Use" ^ string_of_int (i + 1) ^ ":\n");
92+
printSnippet buf s;
93+
addSegment (Buffer.contents buf));
94+
!segments |> List.rev

analysis/src/Protocol.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ type codeAction = {
8585

8686
let null = "null"
8787
let array l = "[" ^ String.concat ", " l ^ "]"
88+
let array_newline l = "[" ^ String.concat ",\n" l ^ "]"
8889

8990
let stringifyPosition p =
9091
Printf.sprintf {|{"line": %i, "character": %i}|} p.line p.character

analysis/tests/src/CompletionExpressions.res

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,3 +250,25 @@ external commitLocalUpdate: (~updater: RecordSourceSelectorProxy.t => unit) => u
250250

251251
// commitLocalUpdate(~updater=)
252252
// ^com
253+
254+
255+
256+
257+
258+
259+
260+
261+
262+
263+
264+
265+
266+
267+
268+
269+
270+
let x1 = 1
271+
let x2 = 2
272+
let x3 = 3
273+
let x4 = 4
274+

analysis/tests/src/CompletionInferValues.res

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ let reactEventFn = (cb: ReactEvent.Mouse.t => unit) => {
3131
// reactEventFn(event => { event->pr });
3232
// ^com
3333

34-
module Div = {
34+
module Div = {
3535
@react.component
3636
let make = (~onMouseEnter: option<JsxEvent.Mouse.t => unit>=?) => {
3737
let _ = onMouseEnter

analysis/tests/src/expected/Cross.res.txt

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
11
References src/Cross.res 0:17
2-
[
3-
{"uri": "Cross.res", "range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 25}}},
2+
{"prompt":
3+
["A Snippet has the form: {\"file\": \"Hello.res\", \"line\":23, \"code\": \"...the code...\"} where the first line of code is line 23.\nFind Uses of \"UnknownName\" given snippets for its Definition and its Users.\n\nThe input has form:\n Definition:\n snippet\n \n Use1:\n snippet1\n \n Use2:\n snippet2\n ...\n\nYou will produce output of the form:\n- `File.res` line `12`: function `foo` calls `UnknownName` ...\n- `File2.res` line `34`: function `bar` uses `UnknownName` to ...\n\nIgnore any code in the Snippets that is not directly relevant to using \"UnknownName\".\nAdd enough details to understand at high level how \"UnknownName\" is used targeted at a person who is trying to undersand the codebase.\n",
4+
"Definition:\n{\"file\": Cross.res, \"line\": 1, \"code\":\nlet crossRef = References.x\n// ^ref\n\nlet crossRef2 = References.x\n\"}\n",
5+
"Use1:\n{\"file\": Cross.res, \"line\": 4, \"code\":\nlet crossRef = References.x\n// ^ref\n\nlet crossRef2 = References.x\n\nmodule Ref = References\n\nlet crossRef3 = References.x\"}\n",
6+
"Use2:\n{\"file\": Cross.res, \"line\": 6, \"code\":\nlet crossRef = References.x\n// ^ref\n\nlet crossRef2 = References.x\n\nmodule Ref = References\n\nlet crossRef3 = References.x\n\nlet crossRefWithInterface = ReferencesWithInterface.x\"}\n",
7+
"Use3:\n{\"file\": Cross.res, \"line\": 8, \"code\":\n\nlet crossRef2 = References.x\n\nmodule Ref = References\n\nlet crossRef3 = References.x\n\nlet crossRefWithInterface = ReferencesWithInterface.x\n// ^ref\n\"}\n",
8+
"Use4:\n{\"file\": References.res, \"line\": 1, \"code\":\nlet x = 12\n// ^ref\n\nlet a = x\n\"}\n"],
9+
"references":
10+
[{"uri": "Cross.res", "range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 25}}},
411
{"uri": "Cross.res", "range": {"start": {"line": 3, "character": 16}, "end": {"line": 3, "character": 26}}},
512
{"uri": "Cross.res", "range": {"start": {"line": 5, "character": 13}, "end": {"line": 5, "character": 23}}},
613
{"uri": "Cross.res", "range": {"start": {"line": 7, "character": 16}, "end": {"line": 7, "character": 26}}},
7-
{"uri": "References.res", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}}
8-
]
14+
{"uri": "References.res", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}}]}
15+
916

1017
References src/Cross.res 9:31
11-
[
12-
{"uri": "Cross.res", "range": {"start": {"line": 9, "character": 28}, "end": {"line": 9, "character": 51}}},
18+
{"prompt":
19+
["A Snippet has the form: {\"file\": \"Hello.res\", \"line\":23, \"code\": \"...the code...\"} where the first line of code is line 23.\nFind Uses of \"UnknownName\" given snippets for its Definition and its Users.\n\nThe input has form:\n Definition:\n snippet\n \n Use1:\n snippet1\n \n Use2:\n snippet2\n ...\n\nYou will produce output of the form:\n- `File.res` line `12`: function `foo` calls `UnknownName` ...\n- `File2.res` line `34`: function `bar` uses `UnknownName` to ...\n\nIgnore any code in the Snippets that is not directly relevant to using \"UnknownName\".\nAdd enough details to understand at high level how \"UnknownName\" is used targeted at a person who is trying to undersand the codebase.\n",
20+
"Definition:\n{\"file\": Cross.res, \"line\": 10, \"code\":\n\nmodule Ref = References\n\nlet crossRef3 = References.x\n\nlet crossRefWithInterface = ReferencesWithInterface.x\n// ^ref\n\nlet crossRefWithInterface2 = ReferencesWithInterface.x\n\"}\n",
21+
"Use1:\n{\"file\": Cross.res, \"line\": 13, \"code\":\nlet crossRef3 = References.x\n\nlet crossRefWithInterface = ReferencesWithInterface.x\n// ^ref\n\nlet crossRefWithInterface2 = ReferencesWithInterface.x\n\nmodule RefWithInterface = ReferencesWithInterface\n\nlet crossRefWithInterface3 = ReferencesWithInterface.x\"}\n",
22+
"Use2:\n{\"file\": Cross.res, \"line\": 15, \"code\":\nlet crossRefWithInterface = ReferencesWithInterface.x\n// ^ref\n\nlet crossRefWithInterface2 = ReferencesWithInterface.x\n\nmodule RefWithInterface = ReferencesWithInterface\n\nlet crossRefWithInterface3 = ReferencesWithInterface.x\n\nlet _ = RenameWithInterface.x\"}\n",
23+
"Use3:\n{\"file\": Cross.res, \"line\": 17, \"code\":\n\nlet crossRefWithInterface2 = ReferencesWithInterface.x\n\nmodule RefWithInterface = ReferencesWithInterface\n\nlet crossRefWithInterface3 = ReferencesWithInterface.x\n\nlet _ = RenameWithInterface.x\n// ^ren RenameWithInterfacePrime\n\"}\n",
24+
"Use4:\n{\"file\": ReferencesWithInterface.res, \"line\": 1, \"code\":\nlet x = 2\"}\n",
25+
"Use5:\n{\"file\": ReferencesWithInterface.resi, \"line\": 1, \"code\":\nlet x: int\"}\n"],
26+
"references":
27+
[{"uri": "Cross.res", "range": {"start": {"line": 9, "character": 28}, "end": {"line": 9, "character": 51}}},
1328
{"uri": "Cross.res", "range": {"start": {"line": 12, "character": 29}, "end": {"line": 12, "character": 52}}},
1429
{"uri": "Cross.res", "range": {"start": {"line": 14, "character": 26}, "end": {"line": 14, "character": 49}}},
1530
{"uri": "Cross.res", "range": {"start": {"line": 16, "character": 29}, "end": {"line": 16, "character": 52}}},
1631
{"uri": "ReferencesWithInterface.res", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}},
17-
{"uri": "ReferencesWithInterface.resi", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}}
18-
]
32+
{"uri": "ReferencesWithInterface.resi", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}}]}
33+
1934

2035
Rename src/Cross.res 18:13 RenameWithInterfacePrime
2136
[

0 commit comments

Comments
 (0)