Skip to content

Commit 4df9ccc

Browse files
committed
initial setup of expression completion
1 parent 9863965 commit 4df9ccc

File tree

5 files changed

+175
-21
lines changed

5 files changed

+175
-21
lines changed

analysis/src/CompletionBackEnd.ml

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,6 +1830,56 @@ let rec resolveNestedPattern typ ~env ~package ~nested =
18301830
typ |> resolveNestedPattern ~env ~package ~nested
18311831
| _ -> None)
18321832

1833+
(** This moves through a pattern via a set of instructions, trying to resolve the type at the end of the pattern. *)
1834+
let rec resolveNestedExpr typ ~env ~package ~nested =
1835+
match nested with
1836+
| [] -> Some (typ, env, None)
1837+
| patternPath :: nested -> (
1838+
match (patternPath, typ |> extractType ~env ~package) with
1839+
| Completable.ETupleItem {itemNum}, Some (Tuple (env, tupleItems, _)) -> (
1840+
match List.nth_opt tupleItems itemNum with
1841+
| None -> None
1842+
| Some typ -> typ |> resolveNestedExpr ~env ~package ~nested)
1843+
| EFollowRecordField {fieldName}, Some (Trecord {env; fields}) -> (
1844+
match
1845+
fields
1846+
|> List.find_opt (fun (field : field) -> field.fname.txt = fieldName)
1847+
with
1848+
| None -> None
1849+
| Some {typ} -> typ |> resolveNestedExpr ~env ~package ~nested)
1850+
| ERecordBody {seenFields}, Some (Trecord {env; typeExpr}) ->
1851+
Some (typeExpr, env, Some (Completable.RecordField {seenFields}))
1852+
| ( EVariantPayload {constructorName = "Some"; itemNum = 0},
1853+
Some (Toption (env, typ)) ) ->
1854+
typ |> resolveNestedExpr ~env ~package ~nested
1855+
| ( EVariantPayload {constructorName; itemNum},
1856+
Some (Tvariant {env; constructors}) ) -> (
1857+
match
1858+
constructors
1859+
|> List.find_opt (fun (c : Constructor.t) ->
1860+
c.cname.txt = constructorName)
1861+
with
1862+
| None -> None
1863+
| Some constructor -> (
1864+
match List.nth_opt constructor.args itemNum with
1865+
| None -> None
1866+
| Some (typ, _) -> typ |> resolveNestedExpr ~env ~package ~nested))
1867+
| ( EPolyvariantPayload {constructorName; itemNum},
1868+
Some (Tpolyvariant {env; constructors}) ) -> (
1869+
match
1870+
constructors
1871+
|> List.find_opt (fun (c : polyVariantConstructor) ->
1872+
c.name = constructorName)
1873+
with
1874+
| None -> None
1875+
| Some constructor -> (
1876+
match List.nth_opt constructor.args itemNum with
1877+
| None -> None
1878+
| Some typ -> typ |> resolveNestedExpr ~env ~package ~nested))
1879+
| EArray, Some (Tarray (env, typ)) ->
1880+
typ |> resolveNestedExpr ~env ~package ~nested
1881+
| _ -> None)
1882+
18331883
let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover
18341884
(completable : Completable.t) =
18351885
let package = full.package in
@@ -1882,18 +1932,21 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover
18821932
&& (forHover || not (List.mem name identsSeen)))
18831933
|> List.map mkLabel)
18841934
@ keyLabels
1885-
| CjsxPropValue {pathToComponent; prefix; propName} -> (
1935+
| CjsxPropValue {pathToComponent; prefix; propName; nested} -> (
18861936
let targetLabel =
18871937
getJsxLabels ~componentPath:pathToComponent ~findTypeOfValue ~package
18881938
|> List.find_opt (fun (label, _, _) -> label = propName)
18891939
in
18901940
let envWhereCompletionStarted = env in
18911941
match targetLabel with
18921942
| None -> []
1893-
| Some (_, typ, env) ->
1894-
typ
1895-
|> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix
1896-
~expandOption:true ~includeLocalValues:true ~completionContext:None)
1943+
| Some (_, typ, env) -> (
1944+
match typ |> resolveNestedExpr ~env ~package:full.package ~nested with
1945+
| None -> []
1946+
| Some (typ, env, completionContext) ->
1947+
typ
1948+
|> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix
1949+
~expandOption:true ~includeLocalValues:true ~completionContext))
18971950
| Cdecorator prefix ->
18981951
let mkDecorator (name, docstring) =
18991952
{(Completion.create ~name ~kind:(Label "") ~env) with docstring}
@@ -2131,7 +2184,7 @@ Note: The `@react.component` decorator requires the react-jsx config to be set i
21312184
in
21322185
(dec2, doc))
21332186
|> List.map mkDecorator
2134-
| Cargument {functionContextPath; argumentLabel; prefix} -> (
2187+
| Cargument {functionContextPath; argumentLabel; prefix; nested} -> (
21352188
let envWhereCompletionStarted = env in
21362189
let labels =
21372190
match
@@ -2153,16 +2206,20 @@ Note: The `@react.component` decorator requires the react-jsx config to be set i
21532206
| (Labelled n | Optional n) when name = n -> true
21542207
| _ -> false))
21552208
in
2209+
let expandOption =
2210+
match targetLabel with
2211+
| None | Some ((Unlabelled _ | Labelled _), _) -> false
2212+
| Some (Optional _, _) -> true
2213+
in
21562214
match targetLabel with
21572215
| None -> []
2158-
| Some (Optional _, typ) ->
2159-
typ
2160-
|> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix
2161-
~expandOption:true ~includeLocalValues:true ~completionContext:None
2162-
| Some ((Unlabelled _ | Labelled _), typ) ->
2163-
typ
2164-
|> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix
2165-
~expandOption:false ~includeLocalValues:true ~completionContext:None)
2216+
| Some (_, typ) -> (
2217+
match typ |> resolveNestedExpr ~env ~package:full.package ~nested with
2218+
| None -> []
2219+
| Some (typ, env, completionContext) ->
2220+
typ
2221+
|> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix
2222+
~expandOption ~includeLocalValues:true ~completionContext))
21662223
| CnamedArg (cp, prefix, identsSeen) ->
21672224
let labels =
21682225
match

analysis/src/CompletionFrontEnd.ml

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ let isPatternTuple pat =
2323
| Ppat_tuple _ -> true
2424
| _ -> false
2525

26+
let traverseExpr (exp : Parsetree.expression) ~exprPath ~pos =
27+
let someIfHasCursor v =
28+
if exp.pexp_loc |> CursorPosition.locHasCursor ~pos then Some v else None
29+
in
30+
match exp.pexp_desc with
31+
| Pexp_ident {txt = Lident txt} -> someIfHasCursor (txt, exprPath)
32+
| Pexp_construct ({txt = Lident "()"}, _) -> someIfHasCursor ("", exprPath)
33+
| Pexp_construct ({txt = Lident txt}, None) -> someIfHasCursor (txt, exprPath)
34+
| Pexp_variant (label, None) -> someIfHasCursor ("#" ^ label, exprPath)
35+
| Pexp_record ([], _) ->
36+
(* Empty fields means we're in a record body `{}`. Complete for the fields. *)
37+
someIfHasCursor ("", [Completable.ERecordBody {seenFields = []}] @ exprPath)
38+
| _ -> None
39+
2640
type prop = {
2741
name: string;
2842
posStart: int * int;
@@ -60,15 +74,16 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor ~posAfterCompName
6074
None
6175
else if prop.exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then
6276
(* Cursor on expr assigned *)
63-
match extractCompletableArgValueInfo prop.exp with
64-
| Some prefix ->
77+
match traverseExpr prop.exp ~exprPath:[] ~pos:posBeforeCursor with
78+
| Some (prefix, nested) ->
6579
Some
6680
(CjsxPropValue
6781
{
6882
pathToComponent =
6983
Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt;
7084
prefix;
7185
propName = prop.name;
86+
nested;
7287
})
7388
| _ -> None
7489
else if prop.exp.pexp_loc |> Loc.end_ = (Location.none |> Loc.end_) then
@@ -80,6 +95,7 @@ let findJsxPropsCompletable ~jsxProps ~endPos ~posBeforeCursor ~posAfterCompName
8095
Utils.flattenLongIdent ~jsx:true jsxProps.compName.txt;
8196
prefix = "";
8297
propName = prop.name;
98+
nested = [];
8399
})
84100
else None
85101
else loop rest
@@ -163,15 +179,16 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
163179
then Some (Completable.CnamedArg (contextPath, labelled.name, allNames))
164180
else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then
165181
(* Completing in the assignment of labelled argument *)
166-
match extractCompletableArgValueInfo exp with
182+
match traverseExpr exp ~exprPath:[] ~pos:posBeforeCursor with
167183
| None -> None
168-
| Some prefix ->
184+
| Some (prefix, nested) ->
169185
Some
170186
(Cargument
171187
{
172188
functionContextPath = contextPath;
173189
argumentLabel = Labelled labelled.name;
174190
prefix;
191+
nested;
175192
})
176193
else if isExprHole exp then
177194
Some
@@ -180,22 +197,24 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
180197
functionContextPath = contextPath;
181198
argumentLabel = Labelled labelled.name;
182199
prefix = "";
200+
nested = [];
183201
})
184202
else loop rest
185203
| {label = None; exp} :: rest ->
186204
if Res_parsetree_viewer.isTemplateLiteral exp then None
187205
else if exp.pexp_loc |> Loc.hasPos ~pos:posBeforeCursor then
188206
(* Completing in an unlabelled argument *)
189-
match extractCompletableArgValueInfo exp with
207+
match traverseExpr exp ~pos:posBeforeCursor ~exprPath:[] with
190208
| None -> None
191-
| Some prefix ->
209+
| Some (prefix, nested) ->
192210
Some
193211
(Cargument
194212
{
195213
functionContextPath = contextPath;
196214
argumentLabel =
197215
Unlabelled {argumentPosition = !unlabelledCount};
198216
prefix;
217+
nested;
199218
})
200219
else if isExprHole exp then
201220
Some
@@ -204,6 +223,7 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
204223
functionContextPath = contextPath;
205224
argumentLabel = Unlabelled {argumentPosition = !unlabelledCount};
206225
prefix = "";
226+
nested = [];
207227
})
208228
else (
209229
unlabelledCount := !unlabelledCount + 1;
@@ -220,6 +240,7 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
220240
argumentLabel =
221241
Unlabelled {argumentPosition = !unlabelledCount};
222242
prefix = "";
243+
nested = [];
223244
})
224245
else None
225246
in
@@ -235,6 +256,7 @@ let findArgCompletables ~(args : arg list) ~endPos ~posBeforeCursor
235256
functionContextPath = contextPath;
236257
argumentLabel = Unlabelled {argumentPosition = 0};
237258
prefix = "";
259+
nested = [];
238260
})
239261
| _ -> loop args
240262

analysis/src/SharedTypes.ml

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,28 @@ module Completable = struct
574574
^ ")"
575575
| PArray -> "array"
576576

577+
type exprContext = RecordField of {seenFields: string list}
578+
579+
type exprPath =
580+
| ETupleItem of {itemNum: int}
581+
| EFollowRecordField of {fieldName: string}
582+
| ERecordBody of {seenFields: string list}
583+
| EVariantPayload of {constructorName: string; itemNum: int}
584+
| EPolyvariantPayload of {constructorName: string; itemNum: int}
585+
| EArray
586+
587+
let exprPathToString p =
588+
match p with
589+
| ETupleItem {itemNum} -> "tuple($" ^ string_of_int itemNum ^ ")"
590+
| EFollowRecordField {fieldName} -> "recordField(" ^ fieldName ^ ")"
591+
| ERecordBody _ -> "recordBody"
592+
| EVariantPayload {constructorName; itemNum} ->
593+
"variantPayload::" ^ constructorName ^ "($" ^ string_of_int itemNum ^ ")"
594+
| EPolyvariantPayload {constructorName; itemNum} ->
595+
"polyvariantPayload::" ^ constructorName ^ "($" ^ string_of_int itemNum
596+
^ ")"
597+
| EArray -> "array"
598+
577599
type t =
578600
| Cdecorator of string (** e.g. @module *)
579601
| CnamedArg of contextPath * string * string list
@@ -585,12 +607,14 @@ module Completable = struct
585607
| Cargument of {
586608
functionContextPath: contextPath;
587609
argumentLabel: argumentLabel;
610+
nested: exprPath list;
588611
prefix: string;
589612
}
590613
(** e.g. someFunction(~someBoolArg=<com>), complete for the value of `someBoolArg` (true or false). *)
591614
| CjsxPropValue of {
592615
pathToComponent: string list;
593616
propName: string;
617+
nested: exprPath list;
594618
prefix: string;
595619
}
596620
| Cpattern of {
@@ -665,7 +689,7 @@ module Completable = struct
665689
| Cnone -> "Cnone"
666690
| Cjsx (sl1, s, sl2) ->
667691
"Cjsx(" ^ (sl1 |> list) ^ ", " ^ str s ^ ", " ^ (sl2 |> list) ^ ")"
668-
| Cargument {functionContextPath; argumentLabel; prefix} ->
692+
| Cargument {functionContextPath; argumentLabel; prefix; nested} -> (
669693
"Cargument "
670694
^ contextPathToString functionContextPath
671695
^ "("
@@ -675,6 +699,14 @@ module Completable = struct
675699
| Optional name -> "~" ^ name ^ "=?")
676700
^ (if prefix <> "" then "=" ^ prefix else "")
677701
^ ")"
702+
^
703+
match nested with
704+
| [] -> ""
705+
| exprPaths ->
706+
"->"
707+
^ (exprPaths
708+
|> List.map (fun exprPath -> exprPathToString exprPath)
709+
|> String.concat ", "))
678710
| CjsxPropValue {prefix; pathToComponent; propName} ->
679711
"CjsxPropValue " ^ (pathToComponent |> list) ^ " " ^ propName ^ "="
680712
^ prefix

analysis/tests/src/CompletionFunctionArguments.res

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,16 @@ let fnTakingTuple = (arg: (int, int, float)) => {
7676

7777
// let _ = fnTakingTuple()
7878
// ^com
79+
80+
type someRecord = {
81+
age: int,
82+
offline: bool,
83+
online: option<bool>,
84+
}
85+
86+
let fnTakingRecord = (r: someRecord) => {
87+
ignore(r)
88+
}
89+
90+
// let _ = fnTakingRecord({})
91+
// ^com

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ Completable: Cargument Value[someOtherFn]($0=f)
8484
"tags": [],
8585
"detail": "bool",
8686
"documentation": null
87+
}, {
88+
"label": "fnTakingRecord",
89+
"kind": 12,
90+
"tags": [],
91+
"detail": "someRecord => unit",
92+
"documentation": null
8793
}, {
8894
"label": "fnTakingTuple",
8995
"kind": 12,
@@ -243,3 +249,27 @@ Completable: Cargument Value[fnTakingTuple]($0)
243249
"insertTextFormat": 2
244250
}]
245251

252+
Complete src/CompletionFunctionArguments.res 89:27
253+
posCursor:[89:27] posNoWhite:[89:26] Found expr:[89:11->89:29]
254+
Pexp_apply ...[89:11->89:25] (...[89:26->89:28])
255+
Completable: Cargument Value[fnTakingRecord]($0)->recordBody
256+
[{
257+
"label": "age",
258+
"kind": 5,
259+
"tags": [],
260+
"detail": "age: int\n\nsomeRecord",
261+
"documentation": null
262+
}, {
263+
"label": "offline",
264+
"kind": 5,
265+
"tags": [],
266+
"detail": "offline: bool\n\nsomeRecord",
267+
"documentation": null
268+
}, {
269+
"label": "online",
270+
"kind": 5,
271+
"tags": [],
272+
"detail": "online: option<bool>\n\nsomeRecord",
273+
"documentation": null
274+
}]
275+

0 commit comments

Comments
 (0)