From 06468ba1cd35737f3f1dc55f56c2cae6b2ad1bcb Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Wed, 21 Dec 2022 22:08:20 +0100 Subject: [PATCH 01/14] add test demonstrating that pipe completion with an unknown return type does not work, not even after compilation --- analysis/tests/src/CompletionPipeChain.res | 4 ++++ analysis/tests/src/expected/CompletionPipeChain.res.txt | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/analysis/tests/src/CompletionPipeChain.res b/analysis/tests/src/CompletionPipeChain.res index 59c1af258..3c48f27fa 100644 --- a/analysis/tests/src/CompletionPipeChain.res +++ b/analysis/tests/src/CompletionPipeChain.res @@ -58,3 +58,7 @@ let f = int->Integer.increment(2) let _ = [123]->Js.Array2.forEach(v => Js.log(v)) // -> // ^com + +let _ = [123]->Belt.Array.reduce(0, (acc, curr) => acc + curr) +// -> +// ^com diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt index fe947a437..18adb063b 100644 --- a/analysis/tests/src/expected/CompletionPipeChain.res.txt +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -239,3 +239,8 @@ posCursor:[58:5] posNoWhite:[58:4] Found expr:[57:8->0:-1] Completable: Cpath Value[Js, Array2, forEach](Nolabel, Nolabel)-> [] +Complete src/CompletionPipeChain.res 62:5 +posCursor:[62:5] posNoWhite:[62:4] Found expr:[61:8->0:-1] +Completable: Cpath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel)-> +[] + From af5d85ac0bbeb222e8b3f17fc3128a2bade149d9 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Wed, 21 Dec 2022 22:13:05 +0100 Subject: [PATCH 02/14] make CPPipe take inline record --- analysis/src/CompletionBackEnd.ml | 2 +- analysis/src/CompletionFrontEnd.ml | 4 ++-- analysis/src/SharedTypes.ml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 10463e787..0772eeb7c 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1275,7 +1275,7 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos else None) | None -> []) | None -> []) - | CPPipe (cp, funNamePrefix) -> ( + | CPPipe {contextPath = cp; id = funNamePrefix} -> ( match cp |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index d7f12cac5..fd34755e5 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -436,11 +436,11 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = | None -> ( match exprToContextPath lhs with | Some pipe -> - setResult (Cpath (CPPipe (pipe, id))); + setResult (Cpath (CPPipe {contextPath = pipe; id})); true | None -> false) | Some pipe -> - setResult (Cpath (CPPipe (pipe, id))); + setResult (Cpath (CPPipe {contextPath = pipe; id})); true in match expr.pexp_desc with diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index e5ee97e9f..77ea64373 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -479,7 +479,7 @@ module Completable = struct | CPId of string list * completionContext | CPField of contextPath * string | CPObj of contextPath * string - | CPPipe of contextPath * string + | CPPipe of {contextPath: contextPath; id: string} type t = | Cdecorator of string (** e.g. @module *) @@ -513,7 +513,7 @@ module Completable = struct completionContextToString completionContext ^ list sl | CPField (cp, s) -> contextPathToString cp ^ "." ^ str s | CPObj (cp, s) -> contextPathToString cp ^ "[\"" ^ s ^ "\"]" - | CPPipe (cp, s) -> contextPathToString cp ^ "->" ^ s + | CPPipe {contextPath; id} -> contextPathToString contextPath ^ "->" ^ id in function | Cpath cp -> "Cpath " ^ contextPathToString cp From 488df414bea529b109bbf1bda43ce74dcbc6792c Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Wed, 21 Dec 2022 22:17:20 +0100 Subject: [PATCH 03/14] propagate loc of lhs in pipe completion --- analysis/src/CompletionFrontEnd.ml | 9 ++++++--- analysis/src/SharedTypes.ml | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index fd34755e5..d9509abde 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -180,6 +180,7 @@ let completePipeChain ~(lhs : Parsetree.expression) = pexp_loc; pexp_attributes; } + |> Option.map (fun ctxPath -> (ctxPath, d.pexp_loc)) (* When the left side of the pipe we're completing is an identifier application. Example: someArray->filterAllTheGoodStuff-> *) | Pexp_apply @@ -195,6 +196,7 @@ let completePipeChain ~(lhs : Parsetree.expression) = pexp_loc; pexp_attributes; } + |> Option.map (fun ctxPath -> (ctxPath, pexp_loc)) | _ -> None let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = @@ -436,11 +438,12 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = | None -> ( match exprToContextPath lhs with | Some pipe -> - setResult (Cpath (CPPipe {contextPath = pipe; id})); + setResult + (Cpath (CPPipe {contextPath = pipe; id; lhsLoc = lhs.pexp_loc})); true | None -> false) - | Some pipe -> - setResult (Cpath (CPPipe {contextPath = pipe; id})); + | Some (pipe, lhsLoc) -> + setResult (Cpath (CPPipe {contextPath = pipe; id; lhsLoc})); true in match expr.pexp_desc with diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 77ea64373..dba389212 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -479,7 +479,7 @@ module Completable = struct | CPId of string list * completionContext | CPField of contextPath * string | CPObj of contextPath * string - | CPPipe of {contextPath: contextPath; id: string} + | CPPipe of {contextPath: contextPath; id: string; lhsLoc: Warnings.loc} type t = | Cdecorator of string (** e.g. @module *) From a705a86db5247841b12db4f9eeba1a7b9bec78aa Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Wed, 21 Dec 2022 22:29:52 +0100 Subject: [PATCH 04/14] look up compiled type parameter value when completing unknown type in pipe chain --- analysis/src/CompletionBackEnd.ml | 25 +++++++++- .../src/expected/CompletionPipeChain.res.txt | 50 ++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 0772eeb7c..0047df27a 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1157,6 +1157,17 @@ let completionsGetTypeEnv = function | {Completion.kind = Field ({typ}, _); env} :: _ -> Some (typ, env) | _ -> None +let findTypeAtLoc loc ~(env : QueryEnv.t) ~package ~debug = + match Cmt.loadFullCmtFromPath ~path:(env.file.uri |> Uri.toPath) with + | None -> None + | Some full -> ( + match References.getLocItem ~full ~pos:(loc |> Loc.end_) ~debug with + | Some {locType = Typed (_, typExpr, _)} -> ( + match extractFunctionType ~env ~package typExpr with + | args, tRet when args <> [] -> Some tRet + | _ -> None) + | _ -> None) + let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos ~env ~exact ~scope (contextPath : Completable.contextPath) = match contextPath with @@ -1275,7 +1286,7 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos else None) | None -> []) | None -> []) - | CPPipe {contextPath = cp; id = funNamePrefix} -> ( + | CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc} -> ( match cp |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos @@ -1283,6 +1294,18 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos |> completionsGetTypeEnv with | Some (typ, envFromCompletionItem) -> ( + (* If the type we're completing on is a type parameter, we won't be able to do + completion unless we know what that type parameter is compiled as. This + attempts to look up the compiled type for that type parameter by looking + for compiled information at the loc of that expression. *) + let typ = + match typ with + | {Types.desc = Tvar _} -> ( + match findTypeAtLoc lhsLoc ~env ~package ~debug:false with + | None -> typ + | Some typFromLoc -> typFromLoc) + | _ -> typ + in let { arrayModulePath; optionModulePath; diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt index 18adb063b..4c19282f3 100644 --- a/analysis/tests/src/expected/CompletionPipeChain.res.txt +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -242,5 +242,53 @@ Completable: Cpath Value[Js, Array2, forEach](Nolabel, Nolabel)-> Complete src/CompletionPipeChain.res 62:5 posCursor:[62:5] posNoWhite:[62:4] Found expr:[61:8->0:-1] Completable: Cpath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel)-> -[] +[{ + "label": "Belt.Int.fromString", + "kind": 12, + "tags": [], + "detail": "string => option<int>", + "documentation": {"kind": "markdown", "value": "\n Converts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n ```res example\n Js.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n ```\n"} + }, { + "label": "Belt.Int.*", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Multiplication of two `int` values. Same as the multiplication from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 * 2 === 4) /* true */\n ```\n"} + }, { + "label": "Belt.Int./", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Division of two `int` values. Same as the division from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(4 / 2 === 2); /* true */\n ```\n"} + }, { + "label": "Belt.Int.toString", + "kind": 12, + "tags": [], + "detail": "int => string", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n ```res example\n Js.log(Belt.Int.toString(1) === \"1\") /* true */\n ```\n"} + }, { + "label": "Belt.Int.toFloat", + "kind": 12, + "tags": [], + "detail": "int => float", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `float`.\n\n ```res example\n Js.log(Belt.Int.toFloat(1) === 1.0) /* true */\n ```\n"} + }, { + "label": "Belt.Int.fromFloat", + "kind": 12, + "tags": [], + "detail": "float => int", + "documentation": {"kind": "markdown", "value": "\n Converts a given `float` to an `int`.\n\n ```res example\n Js.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n ```\n"} + }, { + "label": "Belt.Int.-", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Subtraction of two `int` values. Same as the subtraction from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 - 1 === 1) /* true */\n ```\n"} + }, { + "label": "Belt.Int.+", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Addition of two `int` values. Same as the addition from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 + 2 === 4) /* true */\n ```\n"} + }] From c0e4b0c43fcf196bcd9bf2655465da7f31261325 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Wed, 21 Dec 2022 22:31:27 +0100 Subject: [PATCH 05/14] more accurate function name --- analysis/src/CompletionBackEnd.ml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 0047df27a..e26a6f805 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1157,7 +1157,7 @@ let completionsGetTypeEnv = function | {Completion.kind = Field ({typ}, _); env} :: _ -> Some (typ, env) | _ -> None -let findTypeAtLoc loc ~(env : QueryEnv.t) ~package ~debug = +let findReturnTypeOfFunctionAtLoc loc ~(env : QueryEnv.t) ~package ~debug = match Cmt.loadFullCmtFromPath ~path:(env.file.uri |> Uri.toPath) with | None -> None | Some full -> ( @@ -1301,7 +1301,9 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos let typ = match typ with | {Types.desc = Tvar _} -> ( - match findTypeAtLoc lhsLoc ~env ~package ~debug:false with + match + findReturnTypeOfFunctionAtLoc lhsLoc ~env ~package ~debug:false + with | None -> typ | Some typFromLoc -> typFromLoc) | _ -> typ From ba532aca0f18dd6e68f7a8d63af68b22a8317c9c Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Wed, 21 Dec 2022 22:43:08 +0100 Subject: [PATCH 06/14] temp remove broken logo image links so extension can be published again --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index e67a69018..e67716011 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,6 @@ <p align="center">The Official VSCode plugin for ReScript</p> -<p align="center"> - <a href="https://marketplace.visualstudio.com/items?itemName=chenglou92.rescript-vscode"> - <img src="https://vsmarketplacebadge.apphb.com/version/chenglou92.rescript-vscode.svg"/> - <img src="https://vsmarketplacebadge.apphb.com/installs/chenglou92.rescript-vscode.svg"/> - </a> -</p> - <p align="center"> <img src="https://user-images.githubusercontent.com/1909539/101266821-790b1400-3707-11eb-8e9f-fb7e36e660e6.gif"/> </p> From f29cd71911581fd592eb01ddf4d79e9fffee9205 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Wed, 21 Dec 2022 22:08:20 +0100 Subject: [PATCH 07/14] add test demonstrating that pipe completion with an unknown return type does not work, not even after compilation --- analysis/tests/src/CompletionPipeChain.res | 4 ++++ analysis/tests/src/expected/CompletionPipeChain.res.txt | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/analysis/tests/src/CompletionPipeChain.res b/analysis/tests/src/CompletionPipeChain.res index 59c1af258..3c48f27fa 100644 --- a/analysis/tests/src/CompletionPipeChain.res +++ b/analysis/tests/src/CompletionPipeChain.res @@ -58,3 +58,7 @@ let f = int->Integer.increment(2) let _ = [123]->Js.Array2.forEach(v => Js.log(v)) // -> // ^com + +let _ = [123]->Belt.Array.reduce(0, (acc, curr) => acc + curr) +// -> +// ^com diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt index fe947a437..18adb063b 100644 --- a/analysis/tests/src/expected/CompletionPipeChain.res.txt +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -239,3 +239,8 @@ posCursor:[58:5] posNoWhite:[58:4] Found expr:[57:8->0:-1] Completable: Cpath Value[Js, Array2, forEach](Nolabel, Nolabel)-> [] +Complete src/CompletionPipeChain.res 62:5 +posCursor:[62:5] posNoWhite:[62:4] Found expr:[61:8->0:-1] +Completable: Cpath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel)-> +[] + From abc4794517334be9749b7538b5f95fc26e60c9eb Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Wed, 21 Dec 2022 22:13:05 +0100 Subject: [PATCH 08/14] make CPPipe take inline record --- analysis/src/CompletionBackEnd.ml | 2 +- analysis/src/CompletionFrontEnd.ml | 4 ++-- analysis/src/SharedTypes.ml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 10463e787..0772eeb7c 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1275,7 +1275,7 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos else None) | None -> []) | None -> []) - | CPPipe (cp, funNamePrefix) -> ( + | CPPipe {contextPath = cp; id = funNamePrefix} -> ( match cp |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index d7f12cac5..fd34755e5 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -436,11 +436,11 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = | None -> ( match exprToContextPath lhs with | Some pipe -> - setResult (Cpath (CPPipe (pipe, id))); + setResult (Cpath (CPPipe {contextPath = pipe; id})); true | None -> false) | Some pipe -> - setResult (Cpath (CPPipe (pipe, id))); + setResult (Cpath (CPPipe {contextPath = pipe; id})); true in match expr.pexp_desc with diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index e5ee97e9f..77ea64373 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -479,7 +479,7 @@ module Completable = struct | CPId of string list * completionContext | CPField of contextPath * string | CPObj of contextPath * string - | CPPipe of contextPath * string + | CPPipe of {contextPath: contextPath; id: string} type t = | Cdecorator of string (** e.g. @module *) @@ -513,7 +513,7 @@ module Completable = struct completionContextToString completionContext ^ list sl | CPField (cp, s) -> contextPathToString cp ^ "." ^ str s | CPObj (cp, s) -> contextPathToString cp ^ "[\"" ^ s ^ "\"]" - | CPPipe (cp, s) -> contextPathToString cp ^ "->" ^ s + | CPPipe {contextPath; id} -> contextPathToString contextPath ^ "->" ^ id in function | Cpath cp -> "Cpath " ^ contextPathToString cp From 6e3618773e34d48d517b1996b0979e689def2500 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Wed, 21 Dec 2022 22:17:20 +0100 Subject: [PATCH 09/14] propagate loc of lhs in pipe completion --- analysis/src/CompletionFrontEnd.ml | 9 ++++++--- analysis/src/SharedTypes.ml | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index fd34755e5..d9509abde 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -180,6 +180,7 @@ let completePipeChain ~(lhs : Parsetree.expression) = pexp_loc; pexp_attributes; } + |> Option.map (fun ctxPath -> (ctxPath, d.pexp_loc)) (* When the left side of the pipe we're completing is an identifier application. Example: someArray->filterAllTheGoodStuff-> *) | Pexp_apply @@ -195,6 +196,7 @@ let completePipeChain ~(lhs : Parsetree.expression) = pexp_loc; pexp_attributes; } + |> Option.map (fun ctxPath -> (ctxPath, pexp_loc)) | _ -> None let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = @@ -436,11 +438,12 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = | None -> ( match exprToContextPath lhs with | Some pipe -> - setResult (Cpath (CPPipe {contextPath = pipe; id})); + setResult + (Cpath (CPPipe {contextPath = pipe; id; lhsLoc = lhs.pexp_loc})); true | None -> false) - | Some pipe -> - setResult (Cpath (CPPipe {contextPath = pipe; id})); + | Some (pipe, lhsLoc) -> + setResult (Cpath (CPPipe {contextPath = pipe; id; lhsLoc})); true in match expr.pexp_desc with diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 77ea64373..dba389212 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -479,7 +479,7 @@ module Completable = struct | CPId of string list * completionContext | CPField of contextPath * string | CPObj of contextPath * string - | CPPipe of {contextPath: contextPath; id: string} + | CPPipe of {contextPath: contextPath; id: string; lhsLoc: Warnings.loc} type t = | Cdecorator of string (** e.g. @module *) From f7ee910000b42691a415bf3ab54957a13d81b573 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Wed, 21 Dec 2022 22:29:52 +0100 Subject: [PATCH 10/14] look up compiled type parameter value when completing unknown type in pipe chain --- analysis/src/CompletionBackEnd.ml | 25 +++++++++- .../src/expected/CompletionPipeChain.res.txt | 50 ++++++++++++++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 0772eeb7c..0047df27a 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1157,6 +1157,17 @@ let completionsGetTypeEnv = function | {Completion.kind = Field ({typ}, _); env} :: _ -> Some (typ, env) | _ -> None +let findTypeAtLoc loc ~(env : QueryEnv.t) ~package ~debug = + match Cmt.loadFullCmtFromPath ~path:(env.file.uri |> Uri.toPath) with + | None -> None + | Some full -> ( + match References.getLocItem ~full ~pos:(loc |> Loc.end_) ~debug with + | Some {locType = Typed (_, typExpr, _)} -> ( + match extractFunctionType ~env ~package typExpr with + | args, tRet when args <> [] -> Some tRet + | _ -> None) + | _ -> None) + let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos ~env ~exact ~scope (contextPath : Completable.contextPath) = match contextPath with @@ -1275,7 +1286,7 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos else None) | None -> []) | None -> []) - | CPPipe {contextPath = cp; id = funNamePrefix} -> ( + | CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc} -> ( match cp |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos @@ -1283,6 +1294,18 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos |> completionsGetTypeEnv with | Some (typ, envFromCompletionItem) -> ( + (* If the type we're completing on is a type parameter, we won't be able to do + completion unless we know what that type parameter is compiled as. This + attempts to look up the compiled type for that type parameter by looking + for compiled information at the loc of that expression. *) + let typ = + match typ with + | {Types.desc = Tvar _} -> ( + match findTypeAtLoc lhsLoc ~env ~package ~debug:false with + | None -> typ + | Some typFromLoc -> typFromLoc) + | _ -> typ + in let { arrayModulePath; optionModulePath; diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt index 18adb063b..4c19282f3 100644 --- a/analysis/tests/src/expected/CompletionPipeChain.res.txt +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -242,5 +242,53 @@ Completable: Cpath Value[Js, Array2, forEach](Nolabel, Nolabel)-> Complete src/CompletionPipeChain.res 62:5 posCursor:[62:5] posNoWhite:[62:4] Found expr:[61:8->0:-1] Completable: Cpath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel)-> -[] +[{ + "label": "Belt.Int.fromString", + "kind": 12, + "tags": [], + "detail": "string => option<int>", + "documentation": {"kind": "markdown", "value": "\n Converts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n ```res example\n Js.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n ```\n"} + }, { + "label": "Belt.Int.*", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Multiplication of two `int` values. Same as the multiplication from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 * 2 === 4) /* true */\n ```\n"} + }, { + "label": "Belt.Int./", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Division of two `int` values. Same as the division from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(4 / 2 === 2); /* true */\n ```\n"} + }, { + "label": "Belt.Int.toString", + "kind": 12, + "tags": [], + "detail": "int => string", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `string`. Uses the JavaScript `String` constructor under the hood.\n\n ```res example\n Js.log(Belt.Int.toString(1) === \"1\") /* true */\n ```\n"} + }, { + "label": "Belt.Int.toFloat", + "kind": 12, + "tags": [], + "detail": "int => float", + "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `float`.\n\n ```res example\n Js.log(Belt.Int.toFloat(1) === 1.0) /* true */\n ```\n"} + }, { + "label": "Belt.Int.fromFloat", + "kind": 12, + "tags": [], + "detail": "float => int", + "documentation": {"kind": "markdown", "value": "\n Converts a given `float` to an `int`.\n\n ```res example\n Js.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n ```\n"} + }, { + "label": "Belt.Int.-", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Subtraction of two `int` values. Same as the subtraction from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 - 1 === 1) /* true */\n ```\n"} + }, { + "label": "Belt.Int.+", + "kind": 12, + "tags": [], + "detail": "(int, int) => int", + "documentation": {"kind": "markdown", "value": "\n Addition of two `int` values. Same as the addition from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 + 2 === 4) /* true */\n ```\n"} + }] From 6c94636bc7c0bcc77bd2c547f687a533ea508583 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Wed, 21 Dec 2022 22:31:27 +0100 Subject: [PATCH 11/14] more accurate function name --- analysis/src/CompletionBackEnd.ml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 0047df27a..e26a6f805 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1157,7 +1157,7 @@ let completionsGetTypeEnv = function | {Completion.kind = Field ({typ}, _); env} :: _ -> Some (typ, env) | _ -> None -let findTypeAtLoc loc ~(env : QueryEnv.t) ~package ~debug = +let findReturnTypeOfFunctionAtLoc loc ~(env : QueryEnv.t) ~package ~debug = match Cmt.loadFullCmtFromPath ~path:(env.file.uri |> Uri.toPath) with | None -> None | Some full -> ( @@ -1301,7 +1301,9 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos let typ = match typ with | {Types.desc = Tvar _} -> ( - match findTypeAtLoc lhsLoc ~env ~package ~debug:false with + match + findReturnTypeOfFunctionAtLoc lhsLoc ~env ~package ~debug:false + with | None -> typ | Some typFromLoc -> typFromLoc) | _ -> typ From 88f4d171f76851ae1d4a5c9bde6dfb363ea8ded0 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Thu, 22 Dec 2022 13:11:10 +0100 Subject: [PATCH 12/14] Warnings.loc -> Location.t --- analysis/src/SharedTypes.ml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index dba389212..ac56eed2f 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -479,7 +479,12 @@ module Completable = struct | CPId of string list * completionContext | CPField of contextPath * string | CPObj of contextPath * string - | CPPipe of {contextPath: contextPath; id: string; lhsLoc: Warnings.loc} + | CPPipe of { + contextPath: contextPath; + id: string; + lhsLoc: Location.t; + (** The loc item for the left hand side of the pipe. *) + } type t = | Cdecorator of string (** e.g. @module *) From 511f75679474c98fad5216351a6c1c98a2033852 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Thu, 22 Dec 2022 13:11:49 +0100 Subject: [PATCH 13/14] shorten test output --- analysis/tests/src/CompletionPipeChain.res | 4 +- .../src/expected/CompletionPipeChain.res.txt | 42 ++----------------- 2 files changed, 5 insertions(+), 41 deletions(-) diff --git a/analysis/tests/src/CompletionPipeChain.res b/analysis/tests/src/CompletionPipeChain.res index 3c48f27fa..f9bcd2345 100644 --- a/analysis/tests/src/CompletionPipeChain.res +++ b/analysis/tests/src/CompletionPipeChain.res @@ -60,5 +60,5 @@ let _ = [123]->Js.Array2.forEach(v => Js.log(v)) // ^com let _ = [123]->Belt.Array.reduce(0, (acc, curr) => acc + curr) -// -> -// ^com +// ->t +// ^com diff --git a/analysis/tests/src/expected/CompletionPipeChain.res.txt b/analysis/tests/src/expected/CompletionPipeChain.res.txt index 4c19282f3..c7cff492f 100644 --- a/analysis/tests/src/expected/CompletionPipeChain.res.txt +++ b/analysis/tests/src/expected/CompletionPipeChain.res.txt @@ -239,28 +239,10 @@ posCursor:[58:5] posNoWhite:[58:4] Found expr:[57:8->0:-1] Completable: Cpath Value[Js, Array2, forEach](Nolabel, Nolabel)-> [] -Complete src/CompletionPipeChain.res 62:5 -posCursor:[62:5] posNoWhite:[62:4] Found expr:[61:8->0:-1] -Completable: Cpath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel)-> +Complete src/CompletionPipeChain.res 62:6 +posCursor:[62:6] posNoWhite:[62:5] Found expr:[61:8->62:6] +Completable: Cpath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel)->t [{ - "label": "Belt.Int.fromString", - "kind": 12, - "tags": [], - "detail": "string => option<int>", - "documentation": {"kind": "markdown", "value": "\n Converts a given `string` to an `int`. Returns `Some(int)` when the input is a number, `None` otherwise.\n\n ```res example\n Js.log(Belt.Int.fromString(\"1\") === Some(1)) /* true */\n ```\n"} - }, { - "label": "Belt.Int.*", - "kind": 12, - "tags": [], - "detail": "(int, int) => int", - "documentation": {"kind": "markdown", "value": "\n Multiplication of two `int` values. Same as the multiplication from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 * 2 === 4) /* true */\n ```\n"} - }, { - "label": "Belt.Int./", - "kind": 12, - "tags": [], - "detail": "(int, int) => int", - "documentation": {"kind": "markdown", "value": "\n Division of two `int` values. Same as the division from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(4 / 2 === 2); /* true */\n ```\n"} - }, { "label": "Belt.Int.toString", "kind": 12, "tags": [], @@ -272,23 +254,5 @@ Completable: Cpath Value[Belt, Array, reduce](Nolabel, Nolabel, Nolabel)-> "tags": [], "detail": "int => float", "documentation": {"kind": "markdown", "value": "\n Converts a given `int` to a `float`.\n\n ```res example\n Js.log(Belt.Int.toFloat(1) === 1.0) /* true */\n ```\n"} - }, { - "label": "Belt.Int.fromFloat", - "kind": 12, - "tags": [], - "detail": "float => int", - "documentation": {"kind": "markdown", "value": "\n Converts a given `float` to an `int`.\n\n ```res example\n Js.log(Belt.Int.fromFloat(1.0) === 1) /* true */\n ```\n"} - }, { - "label": "Belt.Int.-", - "kind": 12, - "tags": [], - "detail": "(int, int) => int", - "documentation": {"kind": "markdown", "value": "\n Subtraction of two `int` values. Same as the subtraction from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 - 1 === 1) /* true */\n ```\n"} - }, { - "label": "Belt.Int.+", - "kind": 12, - "tags": [], - "detail": "(int, int) => int", - "documentation": {"kind": "markdown", "value": "\n Addition of two `int` values. Same as the addition from `Pervasives`.\n\n ```res example\n open Belt.Int\n Js.log(2 + 2 === 4) /* true */\n ```\n"} }] From 89e1f57222cac3fa41e0858a934522b552cd075c Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn <gabbe.nord@gmail.com> Date: Thu, 22 Dec 2022 13:18:54 +0100 Subject: [PATCH 14/14] get rid of redundant cmt lookup --- analysis/src/Commands.ml | 6 ++-- analysis/src/CompletionBackEnd.ml | 47 +++++++++++++++---------------- analysis/src/Hover.ml | 7 +++-- analysis/src/SignatureHelp.ml | 7 +++-- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/analysis/src/Commands.ml b/analysis/src/Commands.ml index 756c6fb43..db671ca08 100644 --- a/analysis/src/Commands.ml +++ b/analysis/src/Commands.ml @@ -15,10 +15,10 @@ let getCompletions ~debug ~path ~pos ~currentFile ~forHover = (* Only perform expensive ast operations if there are completables *) match Cmt.loadFullCmtFromPath ~path with | None -> [] - | Some {file; package} -> - let env = SharedTypes.QueryEnv.fromFile file in + | Some full -> + let env = SharedTypes.QueryEnv.fromFile full.file in completable - |> CompletionBackEnd.processCompletable ~debug ~package ~pos ~scope ~env + |> CompletionBackEnd.processCompletable ~debug ~full ~pos ~scope ~env ~forHover)) let completion ~debug ~path ~pos ~currentFile = diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index e26a6f805..2b588784a 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1157,19 +1157,17 @@ let completionsGetTypeEnv = function | {Completion.kind = Field ({typ}, _); env} :: _ -> Some (typ, env) | _ -> None -let findReturnTypeOfFunctionAtLoc loc ~(env : QueryEnv.t) ~package ~debug = - match Cmt.loadFullCmtFromPath ~path:(env.file.uri |> Uri.toPath) with - | None -> None - | Some full -> ( - match References.getLocItem ~full ~pos:(loc |> Loc.end_) ~debug with - | Some {locType = Typed (_, typExpr, _)} -> ( - match extractFunctionType ~env ~package typExpr with - | args, tRet when args <> [] -> Some tRet - | _ -> None) +let findReturnTypeOfFunctionAtLoc loc ~(env : QueryEnv.t) ~full ~debug = + match References.getLocItem ~full ~pos:(loc |> Loc.end_) ~debug with + | Some {locType = Typed (_, typExpr, _)} -> ( + match extractFunctionType ~env ~package:full.package typExpr with + | args, tRet when args <> [] -> Some tRet | _ -> None) + | _ -> None -let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos - ~env ~exact ~scope (contextPath : Completable.contextPath) = +let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact ~scope (contextPath : Completable.contextPath) = + let package = full.package in match contextPath with | CPString -> [ @@ -1192,8 +1190,8 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos | CPApply (cp, labels) -> ( match cp - |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos - ~env ~exact:true ~scope + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact:true ~scope |> completionsGetTypeEnv with | Some (typ, env) -> ( @@ -1238,8 +1236,8 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos | CPField (cp, fieldName) -> ( match cp - |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos - ~env ~exact:true ~scope + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact:true ~scope |> completionsGetTypeEnv with | Some (typ, env) -> ( @@ -1261,8 +1259,8 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos | CPObj (cp, label) -> ( match cp - |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos - ~env ~exact:true ~scope + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact:true ~scope |> completionsGetTypeEnv with | Some (typ, env) -> ( @@ -1289,8 +1287,8 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos | CPPipe {contextPath = cp; id = funNamePrefix; lhsLoc} -> ( match cp - |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos - ~env ~exact:true ~scope + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact:true ~scope |> completionsGetTypeEnv with | Some (typ, envFromCompletionItem) -> ( @@ -1302,7 +1300,7 @@ let rec getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos match typ with | {Types.desc = Tvar _} -> ( match - findReturnTypeOfFunctionAtLoc lhsLoc ~env ~package ~debug:false + findReturnTypeOfFunctionAtLoc lhsLoc ~env ~full ~debug:false with | None -> typ | Some typFromLoc -> typFromLoc) @@ -1443,8 +1441,9 @@ let getOpens ~debug ~rawOpens ~package ~env = (* Last open takes priority *) List.rev resolvedOpens -let processCompletable ~debug ~package ~scope ~env ~pos ~forHover +let processCompletable ~debug ~full ~scope ~env ~pos ~forHover (completable : Completable.t) = + let package = full.package in let rawOpens = Scope.getRawOpens scope in let opens = getOpens ~debug ~rawOpens ~package ~env in let allFiles = FileSet.union package.projectFiles package.dependenciesFiles in @@ -1458,8 +1457,8 @@ let processCompletable ~debug ~package ~scope ~env ~pos ~forHover | Cnone -> [] | Cpath contextPath -> contextPath - |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos - ~env ~exact:forHover ~scope + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact:forHover ~scope | Cjsx ([id], prefix, identsSeen) when String.uncapitalize_ascii id = id -> let mkLabel (name, typString) = Completion.create ~name ~kind:(Label typString) ~env @@ -1808,7 +1807,7 @@ Note: The `@react.component` decorator requires the react-jsx config to be set i let labels = match cp - |> getCompletionsForContextPath ~package ~opens ~rawOpens ~allFiles ~pos + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env ~exact:true ~scope |> completionsGetTypeEnv with diff --git a/analysis/src/Hover.ml b/analysis/src/Hover.ml index 599852273..2cfeb8348 100644 --- a/analysis/src/Hover.ml +++ b/analysis/src/Hover.ml @@ -136,12 +136,13 @@ let getHoverViaCompletions ~debug ~path ~pos ~currentFile ~forHover (* Only perform expensive ast operations if there are completables *) match Cmt.loadFullCmtFromPath ~path with | None -> None - | Some {file; package} -> ( + | Some full -> ( + let {file; package} = full in let env = SharedTypes.QueryEnv.fromFile file in let completions = completable - |> CompletionBackEnd.processCompletable ~debug ~package ~pos ~scope - ~env ~forHover + |> CompletionBackEnd.processCompletable ~debug ~full ~pos ~scope ~env + ~forHover in match completions with | {kind = Label typString; docstring} :: _ -> diff --git a/analysis/src/SignatureHelp.ml b/analysis/src/SignatureHelp.ml index 364d463b2..69a0ff006 100644 --- a/analysis/src/SignatureHelp.ml +++ b/analysis/src/SignatureHelp.ml @@ -18,12 +18,13 @@ let findFunctionType ~currentFile ~debug ~path ~pos = | Some (completable, scope) -> ( match Cmt.loadFullCmtFromPath ~path with | None -> None - | Some {file; package} -> + | Some full -> + let {file; package} = full in let env = QueryEnv.fromFile file in Some ( completable - |> CompletionBackEnd.processCompletable ~debug ~package ~pos - ~scope ~env ~forHover:true, + |> CompletionBackEnd.processCompletable ~debug ~full ~pos ~scope + ~env ~forHover:true, env, package, file )))