Skip to content

Commit 7343fca

Browse files
committed
refactor pipe completion to handle builtins properly in new approach when resolving aliased types
1 parent 10a3077 commit 7343fca

File tree

3 files changed

+164
-115
lines changed

3 files changed

+164
-115
lines changed

analysis/src/CompletionBackEnd.ml

+67-94
Original file line numberDiff line numberDiff line change
@@ -719,93 +719,52 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
719719
~exact:true ~scope
720720
|> completionsGetTypeEnv
721721
with
722+
| None -> []
722723
| Some (typ, envFromCompletionItem) -> (
723724
let env, typ =
724-
typ |> TypeUtils.resolveTypeForPipeCompletion ~env ~package
725-
in
726-
727-
(* If the type we're completing on is a type parameter, we won't be able to do
728-
completion unless we know what that type parameter is compiled as. This
729-
attempts to look up the compiled type for that type parameter by looking
730-
for compiled information at the loc of that expression. *)
731-
let typ =
732-
match typ with
733-
| {Types.desc = Tvar _} -> (
734-
match
735-
TypeUtils.findReturnTypeOfFunctionAtLoc lhsLoc ~env ~full
736-
~debug:false
737-
with
738-
| None -> typ
739-
| Some typFromLoc -> typFromLoc)
740-
| _ -> typ
741-
in
742-
let {
743-
arrayModulePath;
744-
optionModulePath;
745-
stringModulePath;
746-
intModulePath;
747-
floatModulePath;
748-
promiseModulePath;
749-
listModulePath;
750-
resultModulePath;
751-
} =
752-
package.builtInCompletionModules
753-
in
754-
let getBuiltinTypePath path =
755-
match path with
756-
| Path.Pident id when Ident.name id = "array" -> Some arrayModulePath
757-
| Path.Pident id when Ident.name id = "option" -> Some optionModulePath
758-
| Path.Pident id when Ident.name id = "string" -> Some stringModulePath
759-
| Path.Pident id when Ident.name id = "int" -> Some intModulePath
760-
| Path.Pident id when Ident.name id = "float" -> Some floatModulePath
761-
| Path.Pident id when Ident.name id = "promise" ->
762-
Some promiseModulePath
763-
| Path.Pident id when Ident.name id = "list" -> Some listModulePath
764-
| Path.Pident id when Ident.name id = "result" -> Some resultModulePath
765-
| Path.Pident id when Ident.name id = "lazy_t" -> Some ["Lazy"]
766-
| Path.Pident id when Ident.name id = "char" -> Some ["Char"]
767-
| Pdot (Pident id, "result", _) when Ident.name id = "Pervasives" ->
768-
Some resultModulePath
769-
| _ -> None
770-
in
771-
let rec expandPath (path : Path.t) =
772-
match path with
773-
| Pident id -> [Ident.name id]
774-
| Pdot (p, s, _) -> s :: expandPath p
775-
| Papply _ -> []
776-
in
777-
let getTypePath typ =
778-
match typ.Types.desc with
779-
| Tconstr (path, _typeArgs, _)
780-
| Tlink {desc = Tconstr (path, _typeArgs, _)}
781-
| Tsubst {desc = Tconstr (path, _typeArgs, _)}
782-
| Tpoly ({desc = Tconstr (path, _typeArgs, _)}, []) ->
783-
Some path
784-
| _ -> None
785-
in
786-
let rec removeRawOpen rawOpen modulePath =
787-
match (rawOpen, modulePath) with
788-
| [_], _ -> Some modulePath
789-
| s :: inner, first :: restPath when s = first ->
790-
removeRawOpen inner restPath
791-
| _ -> None
792-
in
793-
let rec removeRawOpens rawOpens modulePath =
794-
match rawOpens with
795-
| rawOpen :: restOpens -> (
796-
let newModulePath = removeRawOpens restOpens modulePath in
797-
match removeRawOpen rawOpen newModulePath with
798-
| None -> newModulePath
799-
| Some mp -> mp)
800-
| [] -> modulePath
725+
typ
726+
|> TypeUtils.resolveTypeForPipeCompletion ~env ~package ~full ~lhsLoc
801727
in
802728
let completionPath =
803-
match getTypePath typ with
804-
| Some typePath -> (
805-
match getBuiltinTypePath typePath with
806-
| Some path -> Some path
807-
| None -> (
808-
match expandPath typePath with
729+
match typ with
730+
| Builtin (builtin, _) ->
731+
let {
732+
arrayModulePath;
733+
optionModulePath;
734+
stringModulePath;
735+
intModulePath;
736+
floatModulePath;
737+
promiseModulePath;
738+
listModulePath;
739+
resultModulePath;
740+
} =
741+
package.builtInCompletionModules
742+
in
743+
Some
744+
(match builtin with
745+
| Array -> arrayModulePath
746+
| Option -> optionModulePath
747+
| String -> stringModulePath
748+
| Int -> intModulePath
749+
| Float -> floatModulePath
750+
| Promise -> promiseModulePath
751+
| List -> listModulePath
752+
| Result -> resultModulePath
753+
| Lazy -> ["Lazy"]
754+
| Char -> ["Char"])
755+
| TypExpr t -> (
756+
let rec expandPath (path : Path.t) =
757+
match path with
758+
| Pident id -> [Ident.name id]
759+
| Pdot (p, s, _) -> s :: expandPath p
760+
| Papply _ -> []
761+
in
762+
match t.Types.desc with
763+
| Tconstr (path, _typeArgs, _)
764+
| Tlink {desc = Tconstr (path, _typeArgs, _)}
765+
| Tsubst {desc = Tconstr (path, _typeArgs, _)}
766+
| Tpoly ({desc = Tconstr (path, _typeArgs, _)}, []) -> (
767+
match expandPath path with
809768
| _ :: pathRev ->
810769
(* type path is relative to the completion environment
811770
express it from the root of the file *)
@@ -820,11 +779,27 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
820779
else envFromCompletionItem.file.moduleName :: pathFromEnv_
821780
in
822781
Some pathFromEnv
823-
| _ -> None))
824-
| None -> None
782+
| _ -> None)
783+
| _ -> None)
825784
in
826785
match completionPath with
827786
| Some completionPath -> (
787+
let rec removeRawOpen rawOpen modulePath =
788+
match (rawOpen, modulePath) with
789+
| [_], _ -> Some modulePath
790+
| s :: inner, first :: restPath when s = first ->
791+
removeRawOpen inner restPath
792+
| _ -> None
793+
in
794+
let rec removeRawOpens rawOpens modulePath =
795+
match rawOpens with
796+
| rawOpen :: restOpens -> (
797+
let newModulePath = removeRawOpens restOpens modulePath in
798+
match removeRawOpen rawOpen newModulePath with
799+
| None -> newModulePath
800+
| Some mp -> mp)
801+
| [] -> modulePath
802+
in
828803
let completionPathMinusOpens =
829804
completionPath
830805
|> removeRawOpens package.opens
@@ -852,17 +827,16 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
852827
(* We add React element functions to the completion if we're in a JSX context *)
853828
let forJsxCompletion =
854829
if inJsx then
855-
match getTypePath typ with
856-
| Some (Path.Pident id) when Ident.name id = "int" -> Some "int"
857-
| Some (Path.Pident id) when Ident.name id = "float" -> Some "float"
858-
| Some (Path.Pident id) when Ident.name id = "string" ->
859-
Some "string"
860-
| Some (Path.Pident id) when Ident.name id = "array" -> Some "array"
830+
match typ with
831+
| Builtin (Int, t) -> Some ("int", t)
832+
| Builtin (Float, t) -> Some ("float", t)
833+
| Builtin (String, t) -> Some ("string", t)
834+
| Builtin (Array, t) -> Some ("array", t)
861835
| _ -> None
862836
else None
863837
in
864838
match forJsxCompletion with
865-
| Some builtinNameToComplete
839+
| Some (builtinNameToComplete, typ)
866840
when Utils.checkName builtinNameToComplete ~prefix:funNamePrefix
867841
~exact:false ->
868842
[
@@ -878,8 +852,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
878852
]
879853
@ completions
880854
| _ -> completions)
881-
| None -> [])
882-
| None -> [])
855+
| None -> []))
883856
| CTuple ctxPaths ->
884857
(* Turn a list of context paths into a list of type expressions. *)
885858
let typeExrps =

analysis/src/TypeUtils.ml

+84-20
Original file line numberDiff line numberDiff line change
@@ -148,18 +148,90 @@ let rec extractType ~env ~package (t : Types.type_expr) =
148148
Some (Tpolyvariant {env; constructors; typeExpr = t})
149149
| _ -> None
150150

151-
let rec resolveTypeForPipeCompletion ~env ~package (t : Types.type_expr) =
152-
match t.desc with
153-
| Tlink t1 | Tsubst t1 | Tpoly (t1, []) ->
154-
resolveTypeForPipeCompletion ~env ~package t1
155-
(* Don't descend into types named "t". Type t is a convention in the ReScript ecosystem. *)
156-
| Tconstr (path, _, _) when path |> Path.last = "t" -> (env, t)
157-
| Tconstr (path, _, _) -> (
158-
match References.digConstructor ~env ~package path with
159-
| Some (env, {item = {decl = {type_manifest = Some typ}}}) ->
160-
resolveTypeForPipeCompletion ~env ~package typ
161-
| _ -> (env, t))
162-
| _ -> (env, t)
151+
let findReturnTypeOfFunctionAtLoc loc ~(env : QueryEnv.t) ~full ~debug =
152+
match References.getLocItem ~full ~pos:(loc |> Loc.end_) ~debug with
153+
| Some {locType = Typed (_, typExpr, _)} -> (
154+
match extractFunctionType ~env ~package:full.package typExpr with
155+
| args, tRet when args <> [] -> Some tRet
156+
| _ -> None)
157+
| _ -> None
158+
159+
type builtinType =
160+
| Array
161+
| Option
162+
| String
163+
| Int
164+
| Float
165+
| Promise
166+
| List
167+
| Result
168+
| Lazy
169+
| Char
170+
171+
type pipeCompletionType =
172+
| Builtin of builtinType * Types.type_expr
173+
| TypExpr of Types.type_expr
174+
175+
let getBuiltinFromTypePath path =
176+
match path with
177+
| Path.Pident id when Ident.name id = "array" -> Some Array
178+
| Path.Pident id when Ident.name id = "option" -> Some Option
179+
| Path.Pident id when Ident.name id = "string" -> Some String
180+
| Path.Pident id when Ident.name id = "int" -> Some Int
181+
| Path.Pident id when Ident.name id = "float" -> Some Float
182+
| Path.Pident id when Ident.name id = "promise" -> Some Promise
183+
| Path.Pident id when Ident.name id = "list" -> Some List
184+
| Path.Pident id when Ident.name id = "result" -> Some Result
185+
| Path.Pident id when Ident.name id = "lazy_t" -> Some Lazy
186+
| Path.Pident id when Ident.name id = "char" -> Some Char
187+
| Pdot (Pident id, "result", _) when Ident.name id = "Pervasives" ->
188+
Some Result
189+
| _ -> None
190+
191+
let rec resolveTypeForPipeCompletion ~env ~package ~lhsLoc ~full
192+
(t : Types.type_expr) =
193+
let builtin =
194+
match t.desc with
195+
| Tconstr (path, _typeArgs, _)
196+
| Tlink {desc = Tconstr (path, _typeArgs, _)}
197+
| Tsubst {desc = Tconstr (path, _typeArgs, _)}
198+
| Tpoly ({desc = Tconstr (path, _typeArgs, _)}, []) ->
199+
path |> getBuiltinFromTypePath
200+
| _ -> None
201+
in
202+
match builtin with
203+
| Some builtin -> (env, Builtin (builtin, t))
204+
| None -> (
205+
(* If the type we're completing on is a type parameter, we won't be able to
206+
do completion unless we know what that type parameter is compiled as.
207+
This attempts to look up the compiled type for that type parameter by
208+
looking for compiled information at the loc of that expression. *)
209+
let typFromLoc =
210+
match t with
211+
| {Types.desc = Tvar _} -> (
212+
match findReturnTypeOfFunctionAtLoc lhsLoc ~env ~full ~debug:false with
213+
| None -> None
214+
| Some typFromLoc -> Some typFromLoc)
215+
| _ -> None
216+
in
217+
match typFromLoc with
218+
| Some typFromLoc ->
219+
typFromLoc |> resolveTypeForPipeCompletion ~lhsLoc ~env ~package ~full
220+
| None ->
221+
let rec digToRelevantType ~env ~package (t : Types.type_expr) =
222+
match t.desc with
223+
| Tlink t1 | Tsubst t1 | Tpoly (t1, []) ->
224+
digToRelevantType ~env ~package t1
225+
(* Don't descend into types named "t". Type t is a convention in the ReScript ecosystem. *)
226+
| Tconstr (path, _, _) when path |> Path.last = "t" -> (env, TypExpr t)
227+
| Tconstr (path, _, _) -> (
228+
match References.digConstructor ~env ~package path with
229+
| Some (env, {item = {decl = {type_manifest = Some typ}}}) ->
230+
digToRelevantType ~env ~package typ
231+
| _ -> (env, TypExpr t))
232+
| _ -> (env, TypExpr t)
233+
in
234+
digToRelevantType ~env ~package t)
163235

164236
(** This moves through a nested path via a set of instructions, trying to resolve the type at the end of the path. *)
165237
let rec resolveNested (typ : completionType) ~env ~package ~nested =
@@ -224,14 +296,6 @@ let rec resolveNested (typ : completionType) ~env ~package ~nested =
224296
TypeExpr typ |> resolveNested ~env ~package ~nested
225297
| _ -> None)
226298

227-
let findReturnTypeOfFunctionAtLoc loc ~(env : QueryEnv.t) ~full ~debug =
228-
match References.getLocItem ~full ~pos:(loc |> Loc.end_) ~debug with
229-
| Some {locType = Typed (_, typExpr, _)} -> (
230-
match extractFunctionType ~env ~package:full.package typExpr with
231-
| args, tRet when args <> [] -> Some tRet
232-
| _ -> None)
233-
| _ -> None
234-
235299
let getArgs ~env (t : Types.type_expr) ~full =
236300
let rec getArgsLoop ~env (t : Types.type_expr) ~full ~currentArgumentPosition
237301
=

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

+13-1
Original file line numberDiff line numberDiff line change
@@ -1745,5 +1745,17 @@ posCursor:[425:8] posNoWhite:[425:7] Found expr:[425:3->425:8]
17451745
Completable: Cpath Value[ok]->g
17461746
Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder
17471747
Resolved opens 2 Completion.res Completion.res
1748-
[]
1748+
[{
1749+
"label": "Belt.Result.getExn",
1750+
"kind": 12,
1751+
"tags": [],
1752+
"detail": "t<'a, 'b> => 'a",
1753+
"documentation": {"kind": "markdown", "value": "\n `getExn(res)`: when `res` is `Ok(n)`, returns `n` when `res` is `Error(m)`, raise an exception\n\n ```res example\n Belt.Result.getExn(Belt.Result.Ok(42)) == 42\n\n Belt.Result.getExn(Belt.Result.Error(\"Invalid data\")) /* raises exception */\n ```\n"}
1754+
}, {
1755+
"label": "Belt.Result.getWithDefault",
1756+
"kind": 12,
1757+
"tags": [],
1758+
"detail": "(t<'a, 'b>, 'a) => 'a",
1759+
"documentation": {"kind": "markdown", "value": "\n `getWithDefault(res, defaultValue)`: If `res` is `Ok(n)`, returns `n`,\n otherwise `default`\n\n ```res example\n Belt.Result.getWithDefault(Ok(42), 0) == 42\n\n Belt.Result.getWithDefault(Error(\"Invalid Data\"), 0) == 0\n ```\n"}
1760+
}]
17491761

0 commit comments

Comments
 (0)