Skip to content

Commit d7e5ec8

Browse files
committed
make dot completion everywhere actually work
1 parent 40ac61b commit d7e5ec8

27 files changed

+1184
-520
lines changed

analysis/src/CompletionBackEnd.ml

Lines changed: 210 additions & 289 deletions
Large diffs are not rendered by default.

analysis/src/DotCompletionUtils.ml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
let filterRecordFields ~env ~recordAsString ~prefix ~exact fields =
2+
fields
3+
|> Utils.filterMap (fun (field : SharedTypes.field) ->
4+
if Utils.checkName field.fname.txt ~prefix ~exact then
5+
Some
6+
(SharedTypes.Completion.create field.fname.txt ~env
7+
?deprecated:field.deprecated ~docstring:field.docstring
8+
~kind:(SharedTypes.Completion.Field (field, recordAsString)))
9+
else None)
10+
11+
let fieldCompletionsForDotCompletion typ ~env ~package ~prefix ~fieldNameLoc
12+
~exact =
13+
let asObject = typ |> TypeUtils.extractObjectType ~env ~package in
14+
match asObject with
15+
| Some (objEnv, obj) ->
16+
(* Handle obj completion via dot *)
17+
if Debug.verbose () then
18+
Printf.printf "[dot_completion]--> Obj type found:\n";
19+
obj |> TypeUtils.getObjFields
20+
|> Utils.filterMap (fun (field, _typ) ->
21+
if Utils.checkName field ~prefix ~exact then
22+
let fullObjFieldName = Printf.sprintf "[\"%s\"]" field in
23+
Some
24+
(SharedTypes.Completion.create fullObjFieldName ~synthetic:true
25+
~range:fieldNameLoc ~insertText:fullObjFieldName ~env:objEnv
26+
~kind:(SharedTypes.Completion.ObjLabel typ))
27+
else None)
28+
| None -> (
29+
match typ |> TypeUtils.extractRecordType ~env ~package with
30+
| Some (env, fields, typDecl, _path, _attributes) ->
31+
fields
32+
|> filterRecordFields ~env ~prefix ~exact
33+
~recordAsString:
34+
(typDecl.item.decl |> Shared.declToString typDecl.name.txt)
35+
| None -> [])

analysis/src/PipeCompletionUtils.ml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
let addJsxCompletionItems ~mainTypeId ~env ~prefix ~(full : SharedTypes.full)
2+
~rawOpens typ =
3+
match mainTypeId with
4+
| ("array" | "float" | "string" | "int") as builtinNameToComplete ->
5+
if Utils.checkName builtinNameToComplete ~prefix ~exact:false then
6+
let name =
7+
match full.package.genericJsxModule with
8+
| None -> "React." ^ builtinNameToComplete
9+
| Some g ->
10+
g ^ "." ^ builtinNameToComplete
11+
|> String.split_on_char '.'
12+
|> TypeUtils.removeOpensFromCompletionPath ~rawOpens
13+
~package:full.package
14+
|> String.concat "."
15+
in
16+
[
17+
SharedTypes.Completion.create name ~synthetic:true
18+
~includesSnippets:true ~kind:(Value typ) ~env ~sortText:"A"
19+
~docstring:
20+
[
21+
"Turns `" ^ builtinNameToComplete
22+
^ "` into a JSX element so it can be used inside of JSX.";
23+
];
24+
]
25+
else []
26+
| _ -> []

analysis/src/ProcessAttributes.ml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,28 @@ let rec findEditorCompleteFromAttribute attributes =
6464
:: _ ->
6565
Some (Utils.flattenLongIdent path)
6666
| _ :: rest -> findEditorCompleteFromAttribute rest
67+
68+
let rec findEditorCompleteFromAttribute2 ?(modulePaths = []) attributes =
69+
let open Parsetree in
70+
match attributes with
71+
| [] -> modulePaths
72+
| ( {Asttypes.txt = "editor.completeFrom"},
73+
PStr [{pstr_desc = Pstr_eval (payloadExpr, _)}] )
74+
:: rest ->
75+
let items =
76+
match payloadExpr with
77+
| {pexp_desc = Pexp_array items} -> items
78+
| p -> [p]
79+
in
80+
let modulePathsFromArray =
81+
items
82+
|> List.filter_map (fun item ->
83+
match item.Parsetree.pexp_desc with
84+
| Pexp_construct ({txt = path}, None) ->
85+
Some (Utils.flattenLongIdent path)
86+
| _ -> None)
87+
in
88+
findEditorCompleteFromAttribute2
89+
~modulePaths:(modulePathsFromArray @ modulePaths)
90+
rest
91+
| _ :: rest -> findEditorCompleteFromAttribute2 ~modulePaths rest

analysis/src/TypeUtils.ml

Lines changed: 207 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
open SharedTypes
22

3+
let modulePathFromEnv env = env.QueryEnv.file.moduleName :: List.rev env.pathRev
4+
5+
let completionPathFromEnvAndPath env ~path =
6+
modulePathFromEnv env @ List.rev (Utils.expandPath path)
7+
|> List.rev |> List.tl |> List.rev
8+
9+
let getFullTypeId ~env (path : Path.t) =
10+
modulePathFromEnv env @ List.rev (Utils.expandPath path) |> String.concat "."
11+
12+
let fullTypeIdFromDecl ~env ~name ~modulePath =
13+
env.QueryEnv.file.moduleName :: ModulePath.toPath modulePath name
14+
|> String.concat "."
15+
316
let debugLogTypeArgContext {env; typeArgs; typeParams} =
417
Printf.sprintf "Type arg context. env: %s, typeArgs: %s, typeParams: %s\n"
518
(Debug.debugPrintEnv env)
@@ -259,6 +272,28 @@ let rec extractFunctionType ~env ~package typ =
259272
in
260273
loop ~env [] typ
261274

275+
let rec extractFunctionTypeWithEnv ~env ~package typ =
276+
let rec loop ~env acc (t : Types.type_expr) =
277+
match t.desc with
278+
| Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> loop ~env acc t1
279+
| Tarrow (label, tArg, tRet, _) -> loop ~env ((label, tArg) :: acc) tRet
280+
| Tconstr (Pident {name = "function$"}, [t; _], _) ->
281+
extractFunctionTypeWithEnv ~env ~package t
282+
| Tconstr (path, typeArgs, _) -> (
283+
match References.digConstructor ~env ~package path with
284+
| Some
285+
( env,
286+
{
287+
item = {decl = {type_manifest = Some t1; type_params = typeParams}};
288+
} ) ->
289+
let t1 = t1 |> instantiateType ~typeParams ~typeArgs in
290+
loop ~env acc t1
291+
| Some _ -> (List.rev acc, t, env)
292+
| _ -> (List.rev acc, t, env))
293+
| _ -> (List.rev acc, t, env)
294+
in
295+
loop ~env [] typ
296+
262297
let maybeSetTypeArgCtx ?typeArgContextFromTypeManifest ~typeParams ~typeArgs env
263298
=
264299
match typeArgContextFromTypeManifest with
@@ -567,6 +602,37 @@ let rec resolveTypeForPipeCompletion ~env ~package ~lhsLoc ~full
567602
in
568603
digToRelevantType ~env ~package t)
569604

605+
let rec resolveTypeForPipeCompletion2 ~env ~package ~lhsLoc ~full
606+
(t : Types.type_expr) =
607+
(* If the type we're completing on is a type parameter, we won't be able to
608+
do completion unless we know what that type parameter is compiled as.
609+
This attempts to look up the compiled type for that type parameter by
610+
looking for compiled information at the loc of that expression. *)
611+
let typFromLoc =
612+
match t with
613+
| {Types.desc = Tvar _} ->
614+
findReturnTypeOfFunctionAtLoc lhsLoc ~env ~full ~debug:false
615+
| _ -> None
616+
in
617+
match typFromLoc with
618+
| Some typFromLoc ->
619+
typFromLoc |> resolveTypeForPipeCompletion2 ~lhsLoc ~env ~package ~full
620+
| None ->
621+
let rec digToRelevantType ~env ~package (t : Types.type_expr) =
622+
match t.desc with
623+
| Tlink t1 | Tsubst t1 | Tpoly (t1, []) ->
624+
digToRelevantType ~env ~package t1
625+
(* Don't descend into types named "t". Type t is a convention in the ReScript ecosystem. *)
626+
| Tconstr (path, _, _) when path |> Path.last = "t" -> (env, t)
627+
| Tconstr (path, _, _) -> (
628+
match References.digConstructor ~env ~package path with
629+
| Some (env, {item = {decl = {type_manifest = Some typ}}}) ->
630+
digToRelevantType ~env ~package typ
631+
| _ -> (env, t))
632+
| _ -> (env, t)
633+
in
634+
digToRelevantType ~env ~package t
635+
570636
let extractTypeFromResolvedType (typ : Type.t) ~env ~full =
571637
match typ.kind with
572638
| Tuple items -> Some (Tuple (env, items, Ctype.newty (Ttuple items)))
@@ -1074,7 +1140,7 @@ module Codegen = struct
10741140
Ast_helper.Exp.case pat (mkFailWithExp ())))
10751141
end
10761142

1077-
let getPathRelativeToEnv ~debug ~(env : QueryEnv.t) ~envFromItem path =
1143+
let getModulePathRelativeToEnv ~debug ~(env : QueryEnv.t) ~envFromItem path =
10781144
match path with
10791145
| _ :: pathRev ->
10801146
(* type path is relative to the completion environment
@@ -1125,49 +1191,89 @@ let pathToElementProps package =
11251191
| Some g -> (g |> String.split_on_char '.') @ ["Elements"; "props"]
11261192

11271193
(** Extracts module to draw extra completions from for the type, if it has been annotated with @editor.completeFrom. *)
1128-
let getExtraModuleToCompleteFromForType ~env ~full (t : Types.type_expr) =
1194+
let rec getExtraModuleToCompleteFromForType ~env ~full (t : Types.type_expr) =
11291195
match t |> Shared.digConstructor with
11301196
| Some path -> (
11311197
match References.digConstructor ~env ~package:full.package path with
11321198
| None -> None
1133-
(*| Some (env, {item = {decl = {type_manifest = Some t}}}) ->
1199+
| Some (env, {item = {decl = {type_manifest = Some t}}}) ->
11341200
getExtraModuleToCompleteFromForType ~env ~full t
1135-
1136-
This could be commented back in to traverse type aliases.
1137-
Not clear as of now if that makes sense to do or not.
1138-
*)
11391201
| Some (_, {item = {attributes}}) ->
11401202
ProcessAttributes.findEditorCompleteFromAttribute attributes)
11411203
| None -> None
11421204

1205+
module StringSet = Set.Make (String)
1206+
1207+
let rec getExtraModuleTosCompleteFromForType ~env ~full (t : Types.type_expr) =
1208+
let foundModulePaths = ref StringSet.empty in
1209+
let addToModulePaths attributes =
1210+
ProcessAttributes.findEditorCompleteFromAttribute2 attributes
1211+
|> List.iter (fun e ->
1212+
foundModulePaths :=
1213+
StringSet.add (e |> String.concat ".") !foundModulePaths)
1214+
in
1215+
let rec inner ~env ~full (t : Types.type_expr) =
1216+
match t |> Shared.digConstructor with
1217+
| Some path -> (
1218+
match References.digConstructor ~env ~package:full.package path with
1219+
| None -> ()
1220+
| Some (env, {item = {decl = {type_manifest = Some t}; attributes}}) ->
1221+
addToModulePaths attributes;
1222+
inner ~env ~full t
1223+
| Some (_, {item = {attributes}}) -> addToModulePaths attributes)
1224+
| None -> ()
1225+
in
1226+
inner ~env ~full t;
1227+
!foundModulePaths |> StringSet.elements
1228+
|> List.map (fun l -> String.split_on_char '.' l)
1229+
11431230
(** Checks whether the provided type represents a function that takes the provided path
11441231
as the first argument (meaning it's pipeable). *)
1145-
let rec fnTakesTypeAsFirstArg ~env ~full ~lastPath t =
1232+
let rec fnTakesTypeAsFirstArg ~env ~full ~targetTypeId t =
1233+
(*if Debug.verbose () then
1234+
Printf.printf "[fnTakesTypeAsFirstArg] start env: %s\n"
1235+
env.QueryEnv.file.moduleName;*)
11461236
match t.Types.desc with
11471237
| Tlink t1
11481238
| Tsubst t1
11491239
| Tpoly (t1, [])
11501240
| Tconstr (Pident {name = "function$"}, [t1; _], _) ->
1151-
fnTakesTypeAsFirstArg ~env ~full ~lastPath t1
1241+
fnTakesTypeAsFirstArg ~env ~full ~targetTypeId t1
11521242
| Tarrow _ -> (
1153-
match extractFunctionType ~env ~package:full.package t with
1154-
| (Nolabel, t) :: _, _ -> (
1155-
let p = pathFromTypeExpr t in
1156-
match p with
1243+
match extractFunctionTypeWithEnv ~env ~package:full.package t with
1244+
| (Nolabel, t) :: _, _, env -> (
1245+
(*if Debug.verbose () then
1246+
Printf.printf "[fnTakesTypeAsFirstArg] extracted env: %s\n"
1247+
env.QueryEnv.file.moduleName;*)
1248+
let mainTypeId =
1249+
match pathFromTypeExpr t with
1250+
| None -> None
1251+
| Some tPath -> Some (getFullTypeId ~env tPath)
1252+
in
1253+
(*if Debug.verbose () then
1254+
Printf.printf "[filterPipeableFunctions]--> targetTypeId:%s = %s\n"
1255+
targetTypeId
1256+
(Option.value ~default:"None" mainTypeId);*)
1257+
match mainTypeId with
11571258
| None -> false
1158-
| Some p ->
1159-
(*
1160-
Rules:
1161-
- The path p of the current type in the module we're looking at is relative to the current module.
1162-
- The path we're comparing against, `path`, is assumed to belong to this current module, because we're completing from it.
1163-
1164-
Therefore, we can safely pluck out just the last part of the `path`, but need to use the entire name of the current type
1165-
we're comparing with.
1166-
*)
1167-
Path.name p = lastPath || Path.name p = "t")
1259+
| Some mainTypeId -> mainTypeId = targetTypeId)
11681260
| _ -> false)
11691261
| _ -> false
11701262

1263+
let getFirstFnUnlabelledArgType ~env ~full t =
1264+
let labels, _, env =
1265+
extractFunctionTypeWithEnv ~env ~package:full.package t
1266+
in
1267+
let rec findFirstUnlabelledArgType labels =
1268+
match labels with
1269+
| (Asttypes.Nolabel, t) :: _ -> Some t
1270+
| _ :: rest -> findFirstUnlabelledArgType rest
1271+
| [] -> None
1272+
in
1273+
match findFirstUnlabelledArgType labels with
1274+
| Some t -> Some (t, env)
1275+
| _ -> None
1276+
11711277
(** Turns a completion into a pipe completion. *)
11721278
let transformCompletionToPipeCompletion ?(synthetic = false) ~env ~replaceRange
11731279
(completion : Completion.t) =
@@ -1184,16 +1290,70 @@ let transformCompletionToPipeCompletion ?(synthetic = false) ~env ~replaceRange
11841290
synthetic;
11851291
}
11861292

1293+
(** This takes a type expr and the env that type expr was found in, and produces a globally unique
1294+
id for that specific type. The globally unique id is the full path to the type as seen from the root
1295+
of the project. Example: type x in module SomeModule in file SomeFile would get the globally
1296+
unique id `SomeFile.SomeModule.x`.*)
1297+
let rec findRootTypeId ~full ~env (t : Types.type_expr) =
1298+
let debug = Debug.verbose () in
1299+
(* let debug = false in *)
1300+
match t.desc with
1301+
| Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> findRootTypeId ~full ~env t1
1302+
| Tconstr (Pident {name = "function$"}, [t; _], _) ->
1303+
findRootTypeId ~full ~env t
1304+
| Tconstr (path, _, _) -> (
1305+
(* We have a path. Try to dig to its declaration *)
1306+
if debug then
1307+
Printf.printf "[findRootTypeId] path %s, dig\n" (Path.name path);
1308+
match References.digConstructor ~env ~package:full.package path with
1309+
| Some (env, {item = {decl = {type_manifest = Some t1}}}) ->
1310+
if debug then
1311+
Printf.printf "[findRootTypeId] dug up type alias at module path %s \n"
1312+
(modulePathFromEnv env |> String.concat ".");
1313+
findRootTypeId ~full ~env t1
1314+
| Some (env, {item = {name}; modulePath}) ->
1315+
(* if it's a named type, then we know its name will be its module path from the env + its name.*)
1316+
if debug then
1317+
Printf.printf
1318+
"[findRootTypeId] dug up named type at module path %s, from item: %s \n"
1319+
(modulePathFromEnv env |> String.concat ".")
1320+
(ModulePath.toPath modulePath name |> String.concat ".");
1321+
Some (fullTypeIdFromDecl ~env ~name ~modulePath)
1322+
| None ->
1323+
(* If we didn't find anything, then it might be a builtin type. Check it.*)
1324+
if debug then Printf.printf "[findRootTypeId] dug up non-type alias\n";
1325+
if
1326+
Predef.builtin_idents
1327+
|> List.find_opt (fun (_, i) -> Ident.same i (Path.head path))
1328+
|> Option.is_some
1329+
then
1330+
Some
1331+
(if debug then Printf.printf "[findRootTypeId] returning builtin\n";
1332+
Path.name path)
1333+
else None)
1334+
| _ -> None
1335+
11871336
(** Filters out completions that are not pipeable from a list of completions. *)
1188-
let filterPipeableFunctions ~env ~full ?synthetic ?lastPath ?replaceRange
1337+
let filterPipeableFunctions ~env ~full ?synthetic ?targetTypeId ?replaceRange
11891338
completions =
1190-
match lastPath with
1339+
match targetTypeId with
11911340
| None -> completions
1192-
| Some lastPath ->
1341+
| Some targetTypeId ->
11931342
completions
11941343
|> List.filter_map (fun (completion : Completion.t) ->
1195-
match completion.kind with
1196-
| Value t when fnTakesTypeAsFirstArg ~env ~full ~lastPath t -> (
1344+
let thisCompletionItemTypeId =
1345+
match completion.kind with
1346+
| Value t -> (
1347+
match
1348+
getFirstFnUnlabelledArgType ~full ~env:completion.env t
1349+
with
1350+
| None -> None
1351+
| Some (t, envFromLabelledArg) ->
1352+
findRootTypeId ~full ~env:envFromLabelledArg t)
1353+
| _ -> None
1354+
in
1355+
match thisCompletionItemTypeId with
1356+
| Some mainTypeId when mainTypeId = targetTypeId -> (
11971357
match replaceRange with
11981358
| None -> Some completion
11991359
| Some replaceRange ->
@@ -1216,3 +1376,22 @@ let rec getObjFields (texp : Types.type_expr) =
12161376
| Tlink te | Tsubst te | Tpoly (te, []) -> te |> getObjFields
12171377
| Tvar None -> []
12181378
| _ -> []
1379+
1380+
let pathToBuiltin path =
1381+
Predef.builtin_idents
1382+
|> List.find_opt (fun (_, i) -> Ident.same i (Path.head path))
1383+
1384+
let completionPathFromMaybeBuiltin path ~package =
1385+
match pathToBuiltin path with
1386+
| Some ("array", _) -> Some package.builtInCompletionModules.arrayModulePath
1387+
| Some ("option", _) -> Some package.builtInCompletionModules.optionModulePath
1388+
| Some ("string", _) -> Some package.builtInCompletionModules.stringModulePath
1389+
| Some ("int", _) -> Some package.builtInCompletionModules.intModulePath
1390+
| Some ("float", _) -> Some package.builtInCompletionModules.floatModulePath
1391+
| Some ("promise", _) ->
1392+
Some package.builtInCompletionModules.promiseModulePath
1393+
| Some ("list", _) -> Some package.builtInCompletionModules.listModulePath
1394+
| Some ("result", _) -> Some package.builtInCompletionModules.resultModulePath
1395+
| Some ("dict", _) -> Some ["Dict"]
1396+
| Some ("char", _) -> Some ["Char"]
1397+
| _ -> None

0 commit comments

Comments
 (0)