Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test: use AI to summarise call sites #785

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
53 changes: 37 additions & 16 deletions analysis/src/Commands.ml
Original file line number Diff line number Diff line change
Expand Up @@ -140,30 +140,51 @@ let typeDefinition ~path ~pos ~debug =
| Some location -> location |> Protocol.stringifyLocation)

let references ~path ~pos ~debug =
let allLocs =
let allLocs, prompt_segments =
match Cmt.loadFullCmtFromPath ~path with
| None -> []
| None -> ([], [])
| Some full -> (
match References.getLocItem ~full ~pos ~debug with
| None -> []
| None -> ([], [])
| Some locItem ->
let itemName =
match locItem.locType with
| Typed (name, _, _) -> name
| _ -> "UnknownName"
in
let prompt = Prompt.createForReferences itemName in
let allReferences = References.allReferencesForLocItem ~full locItem in
allReferences
|> List.fold_left
(fun acc {References.uri = uri2; locOpt} ->
let loc =
match locOpt with
| Some loc -> loc
| None -> Uri.toTopLevelLoc uri2
in
Protocol.stringifyLocation
{uri = Uri.toString uri2; range = Utils.cmtLocToRange loc}
:: acc)
[])
let references =
allReferences
|> List.fold_left
(fun acc {References.uri = uri2; locOpt} ->
let loc =
match locOpt with
| Some loc -> loc
| None -> Uri.toTopLevelLoc uri2
in
prompt
|> Prompt.addSnippet ~isDefinition:(locItem.loc = loc)
~pos:(Loc.start loc) ~uri:uri2;
Protocol.stringifyLocation
{uri = Uri.toString uri2; range = Utils.cmtLocToRange loc}
:: acc)
[]
in
(references, Prompt.toSegments prompt))
in

print_endline
(if allLocs = [] then Protocol.null
else "[\n" ^ (allLocs |> String.concat ",\n") ^ "\n]")
else
let prompt =
prompt_segments
|> List.map Protocol.wrapInQuotes
|> Protocol.array_newline
in
let references = allLocs |> Protocol.array_newline in
Printf.sprintf "{\"prompt\":\n%s,\n\"references\":\n%s}\n" prompt
references)

let rename ~path ~pos ~newName ~debug =
let result =
Expand Down
94 changes: 94 additions & 0 deletions analysis/src/Prompt.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
type lines = string array
type snippet = {uri: Uri.t; pos: Pos.t; code: string}
type t = {
mutable definition: snippet option;
mutable files: (string, lines) Hashtbl.t;
mutable preamble: string;
mutable snippets: snippet list;
}

let readFile {files} name =
match Hashtbl.find_opt files name with
| None -> (
match Files.readFile name with
| None -> None
| Some text -> Some (text |> String.split_on_char '\n' |> Array.of_list))
| Some lines -> Some lines

let snippetRadius (lines : lines) = min (Array.length lines) 10 / 2

let addSnippet ~isDefinition ~pos ~(uri : Uri.t) prompt =
match readFile prompt (Uri.toPath uri) with
| None -> () (* ignore files not found *)
| Some lines ->
let lineNum = fst pos in
let radius = snippetRadius lines in
let firstLine = max 0 (lineNum - radius) in
let lastLine = min (Array.length lines - 1) (lineNum + radius) in
let linesInRadius = Array.sub lines firstLine (lastLine - firstLine) in
let code = linesInRadius |> Array.to_list |> String.concat "\n" in
let snippet = {uri; pos; code} in
if isDefinition then prompt.definition <- Some snippet
else prompt.snippets <- snippet :: prompt.snippets

let printSnippet buf {uri; pos; code} =
Buffer.add_string buf
("{\"file\": "
^ Filename.basename (Uri.toString uri)
^ ", \"line\": "
^ string_of_int (1 + fst pos)
^ ", \"code\":\n");
Buffer.add_string buf code;
Buffer.add_string buf "\"}\n"

let createForReferences name =
let quoted = "\"" ^ name ^ "\"" in
let backticked = "`" ^ name ^ "`" in
let preamble =
[
{|A Snippet has the form: {"file": "Hello.res", "line":23, "code": "...the code..."} where the first line of code is line 23.|};
"Find Uses of " ^ quoted
^ " given snippets for its Definition and its Users.";
"";
{|The input has form:
Definition:
snippet

Use1:
snippet1

Use2:
snippet2
...|};
"";
"You will produce output of the form:";
"- `File.res` line `12`: function `foo` calls " ^ backticked ^ " ...";
"- `File2.res` line `34`: function `bar` uses " ^ backticked ^ " to ...";
"";
"Ignore any code in the Snippets that is not directly relevant to using "
^ quoted ^ ".";
"Add enough details to understand at high level how " ^ quoted
^ " is used targeted at a person who is trying to undersand the codebase.";
]
|> String.concat "\n"
in
{definition = None; files = Hashtbl.create 1; preamble; snippets = []}

let toSegments {definition; preamble; snippets} =
let segments = ref [] in
let addSegment s = segments := s :: !segments in
addSegment (preamble ^ "\n");
(match definition with
| None -> ()
| Some snippet ->
let buf = Buffer.create 1 in
Buffer.add_string buf "Definition:\n";
printSnippet buf snippet;
addSegment (Buffer.contents buf));
snippets
|> List.iteri (fun i s ->
let buf = Buffer.create 1 in
Buffer.add_string buf ("Use" ^ string_of_int (i + 1) ^ ":\n");
printSnippet buf s;
addSegment (Buffer.contents buf));
!segments |> List.rev
1 change: 1 addition & 0 deletions analysis/src/Protocol.ml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ type codeAction = {

let null = "null"
let array l = "[" ^ String.concat ", " l ^ "]"
let array_newline l = "[" ^ String.concat ",\n" l ^ "]"

let stringifyPosition p =
Printf.sprintf {|{"line": %i, "character": %i}|} p.line p.character
Expand Down
22 changes: 22 additions & 0 deletions analysis/tests/src/CompletionExpressions.res
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,25 @@ external commitLocalUpdate: (~updater: RecordSourceSelectorProxy.t => unit) => u

// commitLocalUpdate(~updater=)
// ^com

















let x1 = 1
let x2 = 2
let x3 = 3
let x4 = 4

2 changes: 1 addition & 1 deletion analysis/tests/src/CompletionInferValues.res
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ let reactEventFn = (cb: ReactEvent.Mouse.t => unit) => {
// reactEventFn(event => { event->pr });
// ^com

module Div = {
module Div = {
@react.component
let make = (~onMouseEnter: option<JsxEvent.Mouse.t => unit>=?) => {
let _ = onMouseEnter
Expand Down
31 changes: 23 additions & 8 deletions analysis/tests/src/expected/Cross.res.txt
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
References src/Cross.res 0:17
[
{"uri": "Cross.res", "range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 25}}},
{"prompt":
["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",
"Definition:\n{\"file\": Cross.res, \"line\": 1, \"code\":\nlet crossRef = References.x\n// ^ref\n\nlet crossRef2 = References.x\n\"}\n",
"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",
"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",
"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",
"Use4:\n{\"file\": References.res, \"line\": 1, \"code\":\nlet x = 12\n// ^ref\n\nlet a = x\n\"}\n"],
"references":
[{"uri": "Cross.res", "range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 25}}},
{"uri": "Cross.res", "range": {"start": {"line": 3, "character": 16}, "end": {"line": 3, "character": 26}}},
{"uri": "Cross.res", "range": {"start": {"line": 5, "character": 13}, "end": {"line": 5, "character": 23}}},
{"uri": "Cross.res", "range": {"start": {"line": 7, "character": 16}, "end": {"line": 7, "character": 26}}},
{"uri": "References.res", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}}
]
{"uri": "References.res", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}}]}


References src/Cross.res 9:31
[
{"uri": "Cross.res", "range": {"start": {"line": 9, "character": 28}, "end": {"line": 9, "character": 51}}},
{"prompt":
["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",
"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",
"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",
"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",
"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",
"Use4:\n{\"file\": ReferencesWithInterface.res, \"line\": 1, \"code\":\nlet x = 2\"}\n",
"Use5:\n{\"file\": ReferencesWithInterface.resi, \"line\": 1, \"code\":\nlet x: int\"}\n"],
"references":
[{"uri": "Cross.res", "range": {"start": {"line": 9, "character": 28}, "end": {"line": 9, "character": 51}}},
{"uri": "Cross.res", "range": {"start": {"line": 12, "character": 29}, "end": {"line": 12, "character": 52}}},
{"uri": "Cross.res", "range": {"start": {"line": 14, "character": 26}, "end": {"line": 14, "character": 49}}},
{"uri": "Cross.res", "range": {"start": {"line": 16, "character": 29}, "end": {"line": 16, "character": 52}}},
{"uri": "ReferencesWithInterface.res", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}},
{"uri": "ReferencesWithInterface.resi", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}}
]
{"uri": "ReferencesWithInterface.resi", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 0, "character": 0}}}]}


Rename src/Cross.res 18:13 RenameWithInterfacePrime
[
Expand Down
Loading