Skip to content

Commit

Permalink
start refactoring dot completion everywhere
Browse files Browse the repository at this point in the history
  • Loading branch information
zth committed Nov 20, 2024
1 parent a881b26 commit 90d3b3b
Show file tree
Hide file tree
Showing 4 changed files with 439 additions and 21 deletions.
126 changes: 112 additions & 14 deletions analysis/src/CompletionBackEnd.ml
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,105 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
path @ [fieldName]
|> getCompletionsForPath ~debug ~opens ~full ~pos ~exact
~completionContext:Field ~env ~scope
| CPField {contextPath = cp; fieldName; fieldNameLoc} when Debug.verbose () ->
(* TODO: this should only happen when the dot completion is at the end of the path *)
if Debug.verbose () then print_endline "[ctx_path]--> dot completion!";
let completionsForCtxPath =
cp
|> getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env
~exact:true ~scope
|> completionsGetTypeEnv2 ~debug ~full ~opens ~rawOpens ~pos
in
(* These are the main completions for the dot. *)
let mainCompletions =
match completionsForCtxPath with
| Some (typ, env)
when typ |> TypeUtils.extractObjectType ~env ~package |> Option.is_some
->
(* Handle obj completion via dot *)
if Debug.verbose () then
Printf.printf "[dot_completion]--> Obj type found:\n";
let objEnv, obj =
typ |> TypeUtils.extractObjectType ~env ~package |> Option.get
in
obj |> TypeUtils.getObjFields
|> Utils.filterMap (fun (field, typ) ->
if Utils.checkName field ~prefix:fieldName ~exact then
let fullObjFieldName = Printf.sprintf "[\"%s\"]" field in
Some
(Completion.create fullObjFieldName ~range:fieldNameLoc
~insertText:fullObjFieldName ~env:objEnv
~kind:(Completion.ObjLabel typ))
else None)
| Some (typ, env)
when typ |> TypeUtils.extractRecordType ~env ~package |> Option.is_some
->
let env, fields, decl, _path, _attributes =
typ |> TypeUtils.extractRecordType ~env ~package |> Option.get
in
if Debug.verbose () then
Printf.printf "[dot_completion]--> Record type found\n";
let recordAsString =
decl.item.decl |> Shared.declToString decl.name.txt
in
fields
|> Utils.filterMap (fun field ->
if Utils.checkName field.fname.txt ~prefix:fieldName ~exact then
Some
(Completion.create field.fname.txt ~env
?deprecated:field.deprecated ~docstring:field.docstring
~kind:(Completion.Field (field, recordAsString)))
else None)
| Some (_typ, _env) ->
(* No more primary completions, for now. *)
[]
| None -> []
in
let pipeCompletions =
match completionsForCtxPath with
| None -> []
| Some (typ, envFromCompletionItem) -> (
let tPath = TypeUtils.pathFromTypeExpr typ in
match tPath with
| None -> []
| Some tPath ->
let completionPath =
(tPath |> Utils.expandPath |> List.tl |> List.rev)
@ (envFromCompletionItem.pathRev |> List.rev)
in
if List.length completionPath = 0 then []
else
let completions =
completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom
~opens ~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens ~full
completionPath
in
completions
|> TypeUtils.filterPipeableFunctions ~env ~full
~lastPath:(Path.last tPath) ~replaceRange:fieldNameLoc)
in
(* Extra completions from configure extra module(s) *)
let extraCompletions =
match completionsForCtxPath with
| None -> []
| Some (typ, envFromCompletionItem) -> (
match
TypeUtils.getExtraModuleToCompleteFromForType typ
~env:envFromCompletionItem ~full
with
| None -> []
| Some completionPath ->
completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens
~pos ~scope ~debug ~prefix:fieldName ~env ~rawOpens ~full
completionPath
|> TypeUtils.filterPipeableFunctions ~env ~full
~replaceRange:fieldNameLoc
?lastPath:
(match TypeUtils.pathFromTypeExpr typ with
| None -> None
| Some tPath -> Some (Path.last tPath)))
in
mainCompletions @ pipeCompletions @ extraCompletions
| CPField {contextPath = cp; fieldName; fieldNameLoc} -> (
if Debug.verbose () then print_endline "[ctx_path]--> CPField";
let completionsForCtxPath =
Expand Down Expand Up @@ -1056,8 +1155,8 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
completionsForPipeFromCompletionPath ~opens ~pos ~scope ~debug
~prefix:fieldName ~envCompletionIsMadeFrom:env ~env:envFromExtracted
~rawOpens ~full completionPath
|> TypeUtils.filterPipeableFunctions ~env:envFromExtracted ~full ~path
~replaceRange:fieldNameLoc
|> TypeUtils.filterPipeableFunctions ~env:envFromExtracted ~full
~lastPath:(Path.last path) ~replaceRange:fieldNameLoc
| None -> []
in
pipeCompletionsForModule
Expand All @@ -1081,16 +1180,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
| Some (typ, env) -> (
match typ |> TypeUtils.extractObjectType ~env ~package with
| Some (env, tObj) ->
let rec getFields (texp : Types.type_expr) =
match texp.desc with
| Tfield (name, _, t1, t2) ->
let fields = t2 |> getFields in
(name, t1) :: fields
| Tlink te | Tsubst te | Tpoly (te, []) -> te |> getFields
| Tvar None -> []
| _ -> []
in
tObj |> getFields
tObj |> TypeUtils.getObjFields
|> Utils.filterMap (fun (field, typ) ->
if Utils.checkName field ~prefix:label ~exact then
Some
Expand Down Expand Up @@ -1175,15 +1265,23 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact
completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens
~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full
completionPath
|> TypeUtils.filterPipeableFunctions ~env ~full ?path:tPath
|> TypeUtils.filterPipeableFunctions ~env ~full
?lastPath:
(match tPath with
| None -> None
| Some tPath -> Some (Path.last tPath))
in
match completionPath with
| Some completionPath -> (
let completionsFromMainFn =
completionsForPipeFromCompletionPath ~envCompletionIsMadeFrom ~opens
~pos ~scope ~debug ~prefix:funNamePrefix ~env ~rawOpens ~full
completionPath
|> TypeUtils.filterPipeableFunctions ~env ~full ?path:tPath
|> TypeUtils.filterPipeableFunctions ~env ~full
?lastPath:
(match tPath with
| None -> None
| Some tPath -> Some (Path.last tPath))
in
let completions = completionsFromMainFn @ completionsFromExtraModule in
(* We add React element functions to the completion if we're in a JSX context *)
Expand Down
23 changes: 16 additions & 7 deletions analysis/src/TypeUtils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1159,13 +1159,13 @@ let getExtraModuleToCompleteFromForType ~env ~full (t : Types.type_expr) =

(** Checks whether the provided type represents a function that takes the provided path
as the first argument (meaning it's pipeable). *)
let rec fnTakesTypeAsFirstArg ~env ~full ~path t =
let rec fnTakesTypeAsFirstArg ~env ~full ~lastPath t =
match t.Types.desc with
| Tlink t1
| Tsubst t1
| Tpoly (t1, [])
| Tconstr (Pident {name = "function$"}, [t1; _], _) ->
fnTakesTypeAsFirstArg ~env ~full ~path t1
fnTakesTypeAsFirstArg ~env ~full ~lastPath t1
| Tarrow _ -> (
match extractFunctionType ~env ~package:full.package t with
| (Nolabel, t) :: _, _ -> (
Expand All @@ -1181,7 +1181,7 @@ let rec fnTakesTypeAsFirstArg ~env ~full ~path t =
Therefore, we can safely pluck out just the last part of the `path`, but need to use the entire name of the current type
we're comparing with.
*)
Path.name p = Path.last path || Path.name p = "t")
Path.name p = lastPath || Path.name p = "t")
| _ -> false)
| _ -> false

Expand All @@ -1201,14 +1201,14 @@ let transformCompletionToPipeCompletion ~env ~replaceRange
}

(** Filters out completions that are not pipeable from a list of completions. *)
let filterPipeableFunctions ~env ~full ?path ?replaceRange completions =
match path with
let filterPipeableFunctions ~env ~full ?lastPath ?replaceRange completions =
match lastPath with
| None -> completions
| Some path ->
| Some lastPath ->
completions
|> List.filter_map (fun (completion : Completion.t) ->
match completion.kind with
| Value t when fnTakesTypeAsFirstArg ~env ~full ~path t -> (
| Value t when fnTakesTypeAsFirstArg ~env ~full ~lastPath t -> (
match replaceRange with
| None -> Some completion
| Some replaceRange ->
Expand All @@ -1222,3 +1222,12 @@ let removeCurrentModuleIfNeeded ~envCompletionIsMadeFrom completionPath =
&& List.hd completionPath = envCompletionIsMadeFrom.QueryEnv.file.moduleName
then List.tl completionPath
else completionPath

let rec getObjFields (texp : Types.type_expr) =
match texp.desc with
| Tfield (name, _, t1, t2) ->
let fields = t2 |> getObjFields in
(name, t1) :: fields
| Tlink te | Tsubst te | Tpoly (te, []) -> te |> getObjFields
| Tvar None -> []
| _ -> []
65 changes: 65 additions & 0 deletions analysis/tests/src/DotCompletionEverywhere.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
let someObj = {
"name": "hello",
"age": 123,
}
// ^dv+
// someObj.
// ^com

// someObj.na
// ^com

type rrr = {name: string}
let rrr = {name: "hello"}

// rrr.n
// ^com

module SomeMod = {
module SomeOtherMod = {
type x

@send external do: x => unit = "do"

external xx: x = "xx"
}
}

external x: SomeMod.SomeOtherMod.x = "x"

// x.
// ^com

// SomeMod.SomeOtherMod.xx.
// ^com

module Sss = {
type rrr = {name: string}
let rrr = {name: "hello"}
let do = rrr => rrr.name
}

// Sss.rrr.
// ^com

@editor.completeFrom(DotCompletionEverywhere.X2)
type x2x2 = {namee: string}
let x2x2 = {namee: "hello"}

module X2 = {
let stuff = x => x.namee
}

// x2x2.
// ^com

let obj = {
"name": "ReScript",
"number": 1,
"nothing": true,
}

// obj.
// ^com

// ^dv-
Loading

0 comments on commit 90d3b3b

Please sign in to comment.