From b2a26a0df78155576a06c86f5a66b027f373dbd9 Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 13 Feb 2025 13:26:16 +0100 Subject: [PATCH 1/6] WIP: better completion for applications --- analysis/src/CompletionBackEnd.ml | 6 ++ analysis/src/TypeUtils.ml | 10 --- .../tests/src/CompletionTaggedTemplate.res | 11 ++- .../tests/src/expected/Completion.res.txt | 10 ++- .../tests/src/expected/CompletionJsx.res.txt | 17 +--- .../expected/CompletionTaggedTemplate.res.txt | 86 ++++++++++++++----- .../expected/DotPipeCompletionSpec.res.txt | 63 ++------------ 7 files changed, 98 insertions(+), 105 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index fb5fcdc252..afb2383b9c 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -965,6 +965,11 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | [], [(Nolabel | Labelled _ | Optional _)] -> (* should not happen, but just ignore extra arguments *) [] in + + (* (match typ.desc with + | Tarrow _ -> print_endline "its an arrow" + | Tconstr _ -> print_endline "it's an alias" + | _ -> print_endline "something else"); *) match TypeUtils.extractFunctionType ~env ~package typ with | args, tRet when args <> [] -> let args = processApply args labels in @@ -1047,6 +1052,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | None -> []) | None -> []) | CPPipe {contextPath = cp; id = prefix; lhsLoc; inJsx; synthetic} -> ( + (* TODO: resolve somewhere in here a potential alias *) if Debug.verbose () then print_endline "[ctx_path]--> CPPipe"; match cp diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 4b2939f970..91d59de08d 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -248,16 +248,6 @@ let extractFunctionType ~env ~package typ = match t.desc with | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> loop ~env acc t1 | Tarrow (label, tArg, tRet, _, _) -> loop ~env ((label, tArg) :: acc) tRet - | Tconstr (path, typeArgs, _) -> ( - match References.digConstructor ~env ~package path with - | Some - ( env, - { - item = {decl = {type_manifest = Some t1; type_params = typeParams}}; - } ) -> - let t1 = t1 |> instantiateType ~typeParams ~typeArgs in - loop ~env acc t1 - | _ -> (List.rev acc, t)) | _ -> (List.rev acc, t) in loop ~env [] typ diff --git a/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res b/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res index dce1a0cbcd..9c054fc5f3 100644 --- a/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res +++ b/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res @@ -1,5 +1,5 @@ module M = { - type t + type t = promise let a = (_t:t) => 4 let b = (_:t) => "c" @@ -9,8 +9,11 @@ module M = { @module("meh") @taggedTemplate external meh: (array, array) => M.t = "default" +let w = meh`` + +// ^dv+ +// let _ = w. +// ^com + // let x = meh`foo`. // ^com - -// let y = meh`bar`.x -// ^com diff --git a/tests/analysis_tests/tests/src/expected/Completion.res.txt b/tests/analysis_tests/tests/src/expected/Completion.res.txt index b87cd58ad2..892f7dc31f 100644 --- a/tests/analysis_tests/tests/src/expected/Completion.res.txt +++ b/tests/analysis_tests/tests/src/expected/Completion.res.txt @@ -1885,13 +1885,19 @@ Resolved opens 2 Completion Completion ContextPath Value[withCallback](~a) ContextPath Value[withCallback] Path withCallback -Found type for function (~b: int) => int +Found type for function (~b: int) => callback [{ "label": "b", "kind": 4, "tags": [], "detail": "int", "documentation": null + }, { + "label": "a", + "kind": 4, + "tags": [], + "detail": "int", + "documentation": null }] Complete src/Completion.res 332:21 @@ -1904,7 +1910,7 @@ Resolved opens 2 Completion Completion ContextPath Value[withCallback](~b) ContextPath Value[withCallback] Path withCallback -Found type for function (~a: int) => int +Found type for function callback [{ "label": "a", "kind": 4, diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt index b4de290007..df863e6656 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt @@ -162,27 +162,16 @@ ContextPath Value[Js, String2, trim](Nolabel)->st <> ContextPath Value[Js, String2, trim](Nolabel) ContextPath Value[Js, String2, trim] Path Js.String2.trim -Path Js.String2.st +CPPipe pathFromEnv: found:true +Path Js_string2.st [{ "label": "React.string", "kind": 12, "tags": [], - "detail": "string", + "detail": "t", "documentation": {"kind": "markdown", "value": "Turns `string` into a JSX element so it can be used inside of JSX."}, "sortText": "A", "insertTextFormat": 2 - }, { - "label": "Js.String2.startsWith", - "kind": 12, - "tags": [], - "detail": "(t, t) => bool", - "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWith(\"ReScript\", \"Re\") == true\nJs.String2.startsWith(\"ReScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Re\") == false\n```\n"} - }, { - "label": "Js.String2.startsWithFrom", - "kind": 12, - "tags": [], - "detail": "(t, t, int) => bool", - "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWithFrom(\"ReScript\", \"Scri\", 2) == true\nJs.String2.startsWithFrom(\"ReScript\", \"\", 2) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Scri\", 2) == false\n```\n"} }] Complete src/CompletionJsx.res 24:19 diff --git a/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt b/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt index 73f0090148..60494ea371 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt @@ -1,15 +1,26 @@ -Complete src/CompletionTaggedTemplate.res 11:20 -posCursor:[11:20] posNoWhite:[11:19] Found expr:[11:11->0:-1] -Completable: Cpath Value[meh](Nolabel, Nolabel)."" + +Complete src/CompletionTaggedTemplate.res 14:13 +[decoratorCompletion] Found @module with non-string payload +posCursor:[14:13] posNoWhite:[14:12] Found expr:[14:11->14:13] +Pexp_field [14:11->14:12] _:[20:0->14:13] +[set_result] set new result to Cpath Value[w]."" +Completable: Cpath Value[w]."" Package opens Pervasives.JsxModules.place holder -ContextPath Value[meh](Nolabel, Nolabel)."" -ContextPath Value[meh](Nolabel, Nolabel) -ContextPath Value[meh] -Path meh -ContextPath Value[meh](Nolabel, Nolabel, Nolabel)-> -ContextPath Value[meh](Nolabel, Nolabel, Nolabel) -ContextPath Value[meh] -Path meh +ContextPath Value[w]."" +[dot_completion]--> Triggered +ContextPath Value[w] +[ctx_path]--> CPId +Path w +[extract_type]--> starting extraction of type: M.t, in env: CompletionTaggedTemplate. Has type arg ctx: false +[extract_type]--> digging for type M.t in CompletionTaggedTemplate +[extract_type]--> found type manifest +[extract_type]--> starting extraction of type: promise, in env: CompletionTaggedTemplate.M. Has type arg ctx: false +ContextPath Value[w]-> +[ctx_path]--> CPPipe +ContextPath Value[w] +[ctx_path]--> CPId +Path w +[pipe_completion] mainTypeId: promise CPPipe pathFromEnv:M found:true Path M. [{ @@ -21,7 +32,7 @@ Path M. "sortText": "xyz", "insertText": "->M.xyz", "additionalTextEdits": [{ - "range": {"start": {"line": 11, "character": 19}, "end": {"line": 11, "character": 20}}, + "range": {"start": {"line": 14, "character": 12}, "end": {"line": 14, "character": 13}}, "newText": "" }] }, { @@ -33,7 +44,7 @@ Path M. "sortText": "b", "insertText": "->M.b", "additionalTextEdits": [{ - "range": {"start": {"line": 11, "character": 19}, "end": {"line": 11, "character": 20}}, + "range": {"start": {"line": 14, "character": 12}, "end": {"line": 14, "character": 13}}, "newText": "" }] }, { @@ -45,25 +56,34 @@ Path M. "sortText": "a", "insertText": "->M.a", "additionalTextEdits": [{ - "range": {"start": {"line": 11, "character": 19}, "end": {"line": 11, "character": 20}}, + "range": {"start": {"line": 14, "character": 12}, "end": {"line": 14, "character": 13}}, "newText": "" }] }] -Complete src/CompletionTaggedTemplate.res 14:21 -posCursor:[14:21] posNoWhite:[14:20] Found expr:[14:11->14:21] -Completable: Cpath Value[meh](Nolabel, Nolabel).x +Complete src/CompletionTaggedTemplate.res 17:20 +[decoratorCompletion] Found @module with non-string payload +[set_result] set new result to Cpath Value[meh](Nolabel, Nolabel)."" +posCursor:[17:20] posNoWhite:[17:19] Found expr:[17:11->0:-1] +Completable: Cpath Value[meh](Nolabel, Nolabel)."" Package opens Pervasives.JsxModules.place holder -ContextPath Value[meh](Nolabel, Nolabel).x +ContextPath Value[meh](Nolabel, Nolabel)."" +[dot_completion]--> Triggered ContextPath Value[meh](Nolabel, Nolabel) +[ctx_path]--> CPApply ContextPath Value[meh] +[ctx_path]--> CPId Path meh -ContextPath Value[meh](Nolabel, Nolabel, Nolabel)->x +ContextPath Value[meh](Nolabel, Nolabel, Nolabel)-> +[ctx_path]--> CPPipe ContextPath Value[meh](Nolabel, Nolabel, Nolabel) +[ctx_path]--> CPApply ContextPath Value[meh] +[ctx_path]--> CPId Path meh +[pipe_completion] mainTypeId: promise CPPipe pathFromEnv:M found:true -Path M.x +Path M. [{ "label": "->M.xyz", "kind": 12, @@ -73,7 +93,31 @@ Path M.x "sortText": "xyz", "insertText": "->M.xyz", "additionalTextEdits": [{ - "range": {"start": {"line": 14, "character": 19}, "end": {"line": 14, "character": 20}}, + "range": {"start": {"line": 17, "character": 19}, "end": {"line": 17, "character": 20}}, + "newText": "" + }] + }, { + "label": "->M.b", + "kind": 12, + "tags": [], + "detail": "t => string", + "documentation": null, + "sortText": "b", + "insertText": "->M.b", + "additionalTextEdits": [{ + "range": {"start": {"line": 17, "character": 19}, "end": {"line": 17, "character": 20}}, + "newText": "" + }] + }, { + "label": "->M.a", + "kind": 12, + "tags": [], + "detail": "t => int", + "documentation": null, + "sortText": "a", + "insertText": "->M.a", + "additionalTextEdits": [{ + "range": {"start": {"line": 17, "character": 19}, "end": {"line": 17, "character": 20}}, "newText": "" }] }] diff --git a/tests/analysis_tests/tests/src/expected/DotPipeCompletionSpec.res.txt b/tests/analysis_tests/tests/src/expected/DotPipeCompletionSpec.res.txt index 0e5dbfbee6..4b8a1ad1a3 100644 --- a/tests/analysis_tests/tests/src/expected/DotPipeCompletionSpec.res.txt +++ b/tests/analysis_tests/tests/src/expected/DotPipeCompletionSpec.res.txt @@ -333,32 +333,9 @@ ContextPath Value[Js, Array2, filter](Nolabel, Nolabel)->filt ContextPath Value[Js, Array2, filter](Nolabel, Nolabel) ContextPath Value[Js, Array2, filter] Path Js.Array2.filter -Path Js.Array2.filt -[{ - "label": "->Js.Array2.filter", - "kind": 12, - "tags": [], - "detail": "(t<'a>, 'a => bool) => t<'a>", - "documentation": {"kind": "markdown", "value": "\nApplies the given predicate function (the second argument) to each element in\nthe array; the result is an array of those elements for which the predicate\nfunction returned `true`. See\n[`Array.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)\non MDN.\n\n## Examples\n\n```rescript\nlet nonEmpty = s => s != \"\"\nJs.Array2.filter([\"abc\", \"\", \"\", \"def\", \"ghi\"], nonEmpty) == [\"abc\", \"def\", \"ghi\"]\n```\n"}, - "sortText": "filter", - "insertText": "->Js.Array2.filter", - "additionalTextEdits": [{ - "range": {"start": {"line": 86, "character": 38}, "end": {"line": 86, "character": 39}}, - "newText": "" - }] - }, { - "label": "->Js.Array2.filteri", - "kind": 12, - "tags": [], - "detail": "(t<'a>, ('a, int) => bool) => t<'a>", - "documentation": {"kind": "markdown", "value": "\nEach element of the given array are passed to the predicate function. The\nreturn value is an array of all those elements for which the predicate function\nreturned `true`.\n\nSee\n[`Array.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)\non MDN.\n\n## Examples\n\n```rescript\n// keep only positive elements at odd indices\nlet positiveOddElement = (item, index) => mod(index, 2) == 1 && item > 0\n\nJs.Array2.filteri([6, 3, 5, 8, 7, -4, 1], positiveOddElement) == [3, 8]\n```\n"}, - "sortText": "filteri", - "insertText": "->Js.Array2.filteri", - "additionalTextEdits": [{ - "range": {"start": {"line": 86, "character": 38}, "end": {"line": 86, "character": 39}}, - "newText": "" - }] - }] +CPPipe pathFromEnv: found:true +Path Js_array2.filt +[] Complete src/DotPipeCompletionSpec.res 89:70 posCursor:[89:70] posNoWhite:[89:69] Found expr:[89:3->89:70] @@ -414,20 +391,9 @@ ContextPath Value[Js, String2, toLowerCase](Nolabel)->toUpperCa ContextPath Value[Js, String2, toLowerCase](Nolabel) ContextPath Value[Js, String2, toLowerCase] Path Js.String2.toLowerCase -Path Js.String2.toUpperCa -[{ - "label": "->Js.String2.toUpperCase", - "kind": 12, - "tags": [], - "detail": "t => t", - "documentation": {"kind": "markdown", "value": "\n`toUpperCase(str)` converts `str` to upper case using the locale-insensitive\ncase mappings in the Unicode Character Database. Notice that the conversion can\nexpand the number of letters in the result; for example the German ß\ncapitalizes to two Ses in a row.\n\nSee [`String.toUpperCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.toUpperCase(\"abc\") == \"ABC\"\nJs.String2.toUpperCase(`Straße`) == `STRASSE`\nJs.String2.toUpperCase(`πς`) == `ΠΣ`\n```\n"}, - "sortText": "toUpperCase", - "insertText": "->Js.String2.toUpperCase", - "additionalTextEdits": [{ - "range": {"start": {"line": 94, "character": 30}, "end": {"line": 94, "character": 31}}, - "newText": "" - }] - }] +CPPipe pathFromEnv: found:true +Path Js_string2.toUpperCa +[] Complete src/DotPipeCompletionSpec.res 97:63 posCursor:[97:63] posNoWhite:[97:62] Found expr:[97:3->97:63] @@ -442,20 +408,9 @@ ContextPath Value[Js, String2, toUpperCase](Nolabel)->toLowerC ContextPath Value[Js, String2, toUpperCase](Nolabel) ContextPath Value[Js, String2, toUpperCase] Path Js.String2.toUpperCase -Path Js.String2.toLowerC -[{ - "label": "->Js.String2.toLowerCase", - "kind": 12, - "tags": [], - "detail": "t => t", - "documentation": {"kind": "markdown", "value": "\n`toLowerCase(str)` converts `str` to lower case using the locale-insensitive\ncase mappings in the Unicode Character Database. Notice that the conversion can\ngive different results depending upon context, for example with the Greek\nletter sigma, which has two different lower case forms; one when it is the last\ncharacter in a string and another when it is not.\n\nSee [`String.toLowerCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.toLowerCase(\"ABC\") == \"abc\"\nJs.String2.toLowerCase(`ΣΠ`) == `σπ`\nJs.String2.toLowerCase(`ΠΣ`) == `πς`\n```\n"}, - "sortText": "toLowerCase", - "insertText": "->Js.String2.toLowerCase", - "additionalTextEdits": [{ - "range": {"start": {"line": 97, "character": 54}, "end": {"line": 97, "character": 55}}, - "newText": "" - }] - }] +CPPipe pathFromEnv: found:true +Path Js_string2.toLowerC +[] Complete src/DotPipeCompletionSpec.res 101:7 posCursor:[101:7] posNoWhite:[101:6] Found expr:[100:9->104:1] From 8f79cd2de08ac6c645a9de98965e5f882fcd75cb Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 13 Feb 2025 20:13:48 +0100 Subject: [PATCH 2/6] Comment out scope thing --- analysis/src/CompletionBackEnd.ml | 4 +- .../tests/src/expected/CompletionJsx.res.txt | 12 +++ .../expected/DotPipeCompletionSpec.res.txt | 102 +++++++++++++++++- 3 files changed, 113 insertions(+), 5 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index afb2383b9c..1803defa4f 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1125,7 +1125,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic ~targetTypeId:mainTypeId - |> List.filter (fun (c : Completion.t) -> + (* |> List.filter (fun (c : Completion.t) -> (* If we're completing from the current module then we need to care about scope. This is automatically taken care of in other cases. *) if isFromCurrentModule then @@ -1139,7 +1139,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | _ -> false) |> Option.is_some | _ -> false - else true) + else true) *) in (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we find and add those completions as well. *) diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt index df863e6656..ca14dd9574 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionJsx.res.txt @@ -172,6 +172,18 @@ Path Js_string2.st "documentation": {"kind": "markdown", "value": "Turns `string` into a JSX element so it can be used inside of JSX."}, "sortText": "A", "insertTextFormat": 2 + }, { + "label": "Js_string2.startsWith", + "kind": 12, + "tags": [], + "detail": "(t, t) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWith(str, substr)` returns `true` if the `str` starts with\n`substr`, `false` otherwise.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWith(\"ReScript\", \"Re\") == true\nJs.String2.startsWith(\"ReScript\", \"\") == true\nJs.String2.startsWith(\"JavaScript\", \"Re\") == false\n```\n"} + }, { + "label": "Js_string2.startsWithFrom", + "kind": 12, + "tags": [], + "detail": "(t, t, int) => bool", + "documentation": {"kind": "markdown", "value": "\nES2015: `startsWithFrom(str, substr, n)` returns `true` if the `str` starts\nwith `substr` starting at position `n`, false otherwise. If `n` is negative,\nthe search starts at the beginning of `str`.\n\nSee [`String.startsWith`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.startsWithFrom(\"ReScript\", \"Scri\", 2) == true\nJs.String2.startsWithFrom(\"ReScript\", \"\", 2) == true\nJs.String2.startsWithFrom(\"JavaScript\", \"Scri\", 2) == false\n```\n"} }] Complete src/CompletionJsx.res 24:19 diff --git a/tests/analysis_tests/tests/src/expected/DotPipeCompletionSpec.res.txt b/tests/analysis_tests/tests/src/expected/DotPipeCompletionSpec.res.txt index 4b8a1ad1a3..3bc091943a 100644 --- a/tests/analysis_tests/tests/src/expected/DotPipeCompletionSpec.res.txt +++ b/tests/analysis_tests/tests/src/expected/DotPipeCompletionSpec.res.txt @@ -281,6 +281,18 @@ Path DotPipeCompletionSpec.SomeOtherModule. "tags": [], "detail": "string", "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} + }, { + "label": "->outOfScope", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => typeOutsideModule", + "documentation": null, + "sortText": "outOfScope", + "insertText": "->outOfScope", + "additionalTextEdits": [{ + "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 7}}, + "newText": "" + }] }, { "label": "->doWithTypeOutsideModule", "kind": 12, @@ -293,6 +305,18 @@ Path DotPipeCompletionSpec.SomeOtherModule. "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 7}}, "newText": "" }] + }, { + "label": "->cc", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => typeOutsideModule", + "documentation": null, + "sortText": "cc", + "insertText": "->cc", + "additionalTextEdits": [{ + "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 7}}, + "newText": "" + }] }, { "label": "->SomeOtherModule.getNName", "kind": 12, @@ -335,7 +359,31 @@ ContextPath Value[Js, Array2, filter] Path Js.Array2.filter CPPipe pathFromEnv: found:true Path Js_array2.filt -[] +[{ + "label": "->Js_array2.filter", + "kind": 12, + "tags": [], + "detail": "(t<'a>, 'a => bool) => t<'a>", + "documentation": {"kind": "markdown", "value": "\nApplies the given predicate function (the second argument) to each element in\nthe array; the result is an array of those elements for which the predicate\nfunction returned `true`. See\n[`Array.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)\non MDN.\n\n## Examples\n\n```rescript\nlet nonEmpty = s => s != \"\"\nJs.Array2.filter([\"abc\", \"\", \"\", \"def\", \"ghi\"], nonEmpty) == [\"abc\", \"def\", \"ghi\"]\n```\n"}, + "sortText": "filter", + "insertText": "->Js_array2.filter", + "additionalTextEdits": [{ + "range": {"start": {"line": 86, "character": 38}, "end": {"line": 86, "character": 39}}, + "newText": "" + }] + }, { + "label": "->Js_array2.filteri", + "kind": 12, + "tags": [], + "detail": "(t<'a>, ('a, int) => bool) => t<'a>", + "documentation": {"kind": "markdown", "value": "\nEach element of the given array are passed to the predicate function. The\nreturn value is an array of all those elements for which the predicate function\nreturned `true`.\n\nSee\n[`Array.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)\non MDN.\n\n## Examples\n\n```rescript\n// keep only positive elements at odd indices\nlet positiveOddElement = (item, index) => mod(index, 2) == 1 && item > 0\n\nJs.Array2.filteri([6, 3, 5, 8, 7, -4, 1], positiveOddElement) == [3, 8]\n```\n"}, + "sortText": "filteri", + "insertText": "->Js_array2.filteri", + "additionalTextEdits": [{ + "range": {"start": {"line": 86, "character": 38}, "end": {"line": 86, "character": 39}}, + "newText": "" + }] + }] Complete src/DotPipeCompletionSpec.res 89:70 posCursor:[89:70] posNoWhite:[89:69] Found expr:[89:3->89:70] @@ -393,7 +441,19 @@ ContextPath Value[Js, String2, toLowerCase] Path Js.String2.toLowerCase CPPipe pathFromEnv: found:true Path Js_string2.toUpperCa -[] +[{ + "label": "->Js_string2.toUpperCase", + "kind": 12, + "tags": [], + "detail": "t => t", + "documentation": {"kind": "markdown", "value": "\n`toUpperCase(str)` converts `str` to upper case using the locale-insensitive\ncase mappings in the Unicode Character Database. Notice that the conversion can\nexpand the number of letters in the result; for example the German ß\ncapitalizes to two Ses in a row.\n\nSee [`String.toUpperCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.toUpperCase(\"abc\") == \"ABC\"\nJs.String2.toUpperCase(`Straße`) == `STRASSE`\nJs.String2.toUpperCase(`πς`) == `ΠΣ`\n```\n"}, + "sortText": "toUpperCase", + "insertText": "->Js_string2.toUpperCase", + "additionalTextEdits": [{ + "range": {"start": {"line": 94, "character": 30}, "end": {"line": 94, "character": 31}}, + "newText": "" + }] + }] Complete src/DotPipeCompletionSpec.res 97:63 posCursor:[97:63] posNoWhite:[97:62] Found expr:[97:3->97:63] @@ -410,7 +470,19 @@ ContextPath Value[Js, String2, toUpperCase] Path Js.String2.toUpperCase CPPipe pathFromEnv: found:true Path Js_string2.toLowerC -[] +[{ + "label": "->Js_string2.toLowerCase", + "kind": 12, + "tags": [], + "detail": "t => t", + "documentation": {"kind": "markdown", "value": "\n`toLowerCase(str)` converts `str` to lower case using the locale-insensitive\ncase mappings in the Unicode Character Database. Notice that the conversion can\ngive different results depending upon context, for example with the Greek\nletter sigma, which has two different lower case forms; one when it is the last\ncharacter in a string and another when it is not.\n\nSee [`String.toLowerCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase)\non MDN.\n\n## Examples\n\n```rescript\nJs.String2.toLowerCase(\"ABC\") == \"abc\"\nJs.String2.toLowerCase(`ΣΠ`) == `σπ`\nJs.String2.toLowerCase(`ΠΣ`) == `πς`\n```\n"}, + "sortText": "toLowerCase", + "insertText": "->Js_string2.toLowerCase", + "additionalTextEdits": [{ + "range": {"start": {"line": 97, "character": 54}, "end": {"line": 97, "character": 55}}, + "newText": "" + }] + }] Complete src/DotPipeCompletionSpec.res 101:7 posCursor:[101:7] posNoWhite:[101:6] Found expr:[100:9->104:1] @@ -433,6 +505,18 @@ Path DotPipeCompletionSpec.SomeOtherModule. "tags": [], "detail": "string", "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} + }, { + "label": "->outOfScope", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => typeOutsideModule", + "documentation": null, + "sortText": "outOfScope", + "insertText": "->outOfScope", + "additionalTextEdits": [{ + "range": {"start": {"line": 101, "character": 6}, "end": {"line": 101, "character": 7}}, + "newText": "" + }] }, { "label": "->doWithTypeOutsideModule", "kind": 12, @@ -445,6 +529,18 @@ Path DotPipeCompletionSpec.SomeOtherModule. "range": {"start": {"line": 101, "character": 6}, "end": {"line": 101, "character": 7}}, "newText": "" }] + }, { + "label": "->cc", + "kind": 12, + "tags": [], + "detail": "typeOutsideModule => typeOutsideModule", + "documentation": null, + "sortText": "cc", + "insertText": "->cc", + "additionalTextEdits": [{ + "range": {"start": {"line": 101, "character": 6}, "end": {"line": 101, "character": 7}}, + "newText": "" + }] }, { "label": "->SomeOtherModule.getNName", "kind": 12, From 97b9f622f11771980388333c222bb225d628debb Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 20 Feb 2025 20:57:50 +0100 Subject: [PATCH 3/6] Uncomment filter --- analysis/src/CompletionBackEnd.ml | 4 +- .../expected/DotPipeCompletionSpec.res.txt | 48 ------------------- 2 files changed, 2 insertions(+), 50 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 70ef4f9845..41c74b5284 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1124,7 +1124,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact ~pos ~scope ~debug ~prefix ~env ~rawOpens ~full completionPath |> TypeUtils.filterPipeableFunctions ~env ~full ~synthetic ~targetTypeId:mainTypeId - (* |> List.filter (fun (c : Completion.t) -> + |> List.filter (fun (c : Completion.t) -> (* If we're completing from the current module then we need to care about scope. This is automatically taken care of in other cases. *) if isFromCurrentModule then @@ -1138,7 +1138,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | _ -> false) |> Option.is_some | _ -> false - else true) *) + else true) in (* Extra completions can be drawn from the @editor.completeFrom attribute. Here we find and add those completions as well. *) diff --git a/tests/analysis_tests/tests/src/expected/DotPipeCompletionSpec.res.txt b/tests/analysis_tests/tests/src/expected/DotPipeCompletionSpec.res.txt index 6fadf24420..3d8952608c 100644 --- a/tests/analysis_tests/tests/src/expected/DotPipeCompletionSpec.res.txt +++ b/tests/analysis_tests/tests/src/expected/DotPipeCompletionSpec.res.txt @@ -275,18 +275,6 @@ Path DotPipeCompletionSpec.SomeOtherModule. "tags": [], "detail": "string", "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} - }, { - "label": "->outOfScope", - "kind": 12, - "tags": [], - "detail": "typeOutsideModule => typeOutsideModule", - "documentation": null, - "sortText": "outOfScope", - "insertText": "->outOfScope", - "additionalTextEdits": [{ - "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 7}}, - "newText": "" - }] }, { "label": "->doWithTypeOutsideModule", "kind": 12, @@ -299,18 +287,6 @@ Path DotPipeCompletionSpec.SomeOtherModule. "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 7}}, "newText": "" }] - }, { - "label": "->cc", - "kind": 12, - "tags": [], - "detail": "typeOutsideModule => typeOutsideModule", - "documentation": null, - "sortText": "cc", - "insertText": "->cc", - "additionalTextEdits": [{ - "range": {"start": {"line": 80, "character": 6}, "end": {"line": 80, "character": 7}}, - "newText": "" - }] }, { "label": "->SomeOtherModule.getNName", "kind": 12, @@ -513,18 +489,6 @@ Path DotPipeCompletionSpec.SomeOtherModule. "tags": [], "detail": "string", "documentation": {"kind": "markdown", "value": "```rescript\nnname: string\n```\n\n```rescript\ntype typeOutsideModule = {nname: string}\n```"} - }, { - "label": "->outOfScope", - "kind": 12, - "tags": [], - "detail": "typeOutsideModule => typeOutsideModule", - "documentation": null, - "sortText": "outOfScope", - "insertText": "->outOfScope", - "additionalTextEdits": [{ - "range": {"start": {"line": 101, "character": 6}, "end": {"line": 101, "character": 7}}, - "newText": "" - }] }, { "label": "->doWithTypeOutsideModule", "kind": 12, @@ -537,18 +501,6 @@ Path DotPipeCompletionSpec.SomeOtherModule. "range": {"start": {"line": 101, "character": 6}, "end": {"line": 101, "character": 7}}, "newText": "" }] - }, { - "label": "->cc", - "kind": 12, - "tags": [], - "detail": "typeOutsideModule => typeOutsideModule", - "documentation": null, - "sortText": "cc", - "insertText": "->cc", - "additionalTextEdits": [{ - "range": {"start": {"line": 101, "character": 6}, "end": {"line": 101, "character": 7}}, - "newText": "" - }] }, { "label": "->SomeOtherModule.getNName", "kind": 12, From 609453c3e4416ff29ccda5d4c20c2645eadb48b7 Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 20 Feb 2025 20:58:47 +0100 Subject: [PATCH 4/6] Remove verbose --- .../tests/src/CompletionTaggedTemplate.res | 1 - .../expected/CompletionTaggedTemplate.res.txt | 43 +++++-------------- 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res b/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res index 9c054fc5f3..df033cd7c8 100644 --- a/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res +++ b/tests/analysis_tests/tests/src/CompletionTaggedTemplate.res @@ -11,7 +11,6 @@ external meh: (array, array) => M.t = "default" let w = meh`` -// ^dv+ // let _ = w. // ^com diff --git a/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt b/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt index 67d418def4..1cbc945f83 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionTaggedTemplate.res.txt @@ -1,27 +1,15 @@ - -Complete src/CompletionTaggedTemplate.res 14:13 -[decoratorCompletion] Found @module with non-string payload -posCursor:[14:13] posNoWhite:[14:12] Found expr:[14:11->14:13] -Pexp_field [14:11->14:12] _:[20:0->14:13] -[set_result] set new result to Cpath Value[w]."" +Complete src/CompletionTaggedTemplate.res 13:13 +posCursor:[13:13] posNoWhite:[13:12] Found expr:[13:11->13:13] +Pexp_field [13:11->13:12] _:[19:0->13:13] Completable: Cpath Value[w]."" Package opens Stdlib.place holder Pervasives.JsxModules.place holder Resolved opens 1 Stdlib ContextPath Value[w]."" -[dot_completion]--> Triggered ContextPath Value[w] -[ctx_path]--> CPId Path w -[extract_type]--> starting extraction of type: M.t, in env: CompletionTaggedTemplate. Has type arg ctx: false -[extract_type]--> digging for type M.t in CompletionTaggedTemplate -[extract_type]--> found type manifest -[extract_type]--> starting extraction of type: promise, in env: CompletionTaggedTemplate.M. Has type arg ctx: false ContextPath Value[w]-> -[ctx_path]--> CPPipe ContextPath Value[w] -[ctx_path]--> CPId Path w -[pipe_completion] mainTypeId: promise CPPipe pathFromEnv:M found:true Path M. [{ @@ -33,7 +21,7 @@ Path M. "sortText": "xyz", "insertText": "->M.xyz", "additionalTextEdits": [{ - "range": {"start": {"line": 14, "character": 12}, "end": {"line": 14, "character": 13}}, + "range": {"start": {"line": 13, "character": 12}, "end": {"line": 13, "character": 13}}, "newText": "" }] }, { @@ -45,7 +33,7 @@ Path M. "sortText": "b", "insertText": "->M.b", "additionalTextEdits": [{ - "range": {"start": {"line": 14, "character": 12}, "end": {"line": 14, "character": 13}}, + "range": {"start": {"line": 13, "character": 12}, "end": {"line": 13, "character": 13}}, "newText": "" }] }, { @@ -57,33 +45,24 @@ Path M. "sortText": "a", "insertText": "->M.a", "additionalTextEdits": [{ - "range": {"start": {"line": 14, "character": 12}, "end": {"line": 14, "character": 13}}, + "range": {"start": {"line": 13, "character": 12}, "end": {"line": 13, "character": 13}}, "newText": "" }] }] -Complete src/CompletionTaggedTemplate.res 17:20 -[decoratorCompletion] Found @module with non-string payload -[set_result] set new result to Cpath Value[meh](Nolabel, Nolabel)."" -posCursor:[17:20] posNoWhite:[17:19] Found expr:[17:11->0:-1] +Complete src/CompletionTaggedTemplate.res 16:20 +posCursor:[16:20] posNoWhite:[16:19] Found expr:[16:11->0:-1] Completable: Cpath Value[meh](Nolabel, Nolabel)."" Package opens Stdlib.place holder Pervasives.JsxModules.place holder Resolved opens 1 Stdlib ContextPath Value[meh](Nolabel, Nolabel)."" -[dot_completion]--> Triggered ContextPath Value[meh](Nolabel, Nolabel) -[ctx_path]--> CPApply ContextPath Value[meh] -[ctx_path]--> CPId Path meh ContextPath Value[meh](Nolabel, Nolabel, Nolabel)-> -[ctx_path]--> CPPipe ContextPath Value[meh](Nolabel, Nolabel, Nolabel) -[ctx_path]--> CPApply ContextPath Value[meh] -[ctx_path]--> CPId Path meh -[pipe_completion] mainTypeId: promise CPPipe pathFromEnv:M found:true Path M. [{ @@ -95,7 +74,7 @@ Path M. "sortText": "xyz", "insertText": "->M.xyz", "additionalTextEdits": [{ - "range": {"start": {"line": 17, "character": 19}, "end": {"line": 17, "character": 20}}, + "range": {"start": {"line": 16, "character": 19}, "end": {"line": 16, "character": 20}}, "newText": "" }] }, { @@ -107,7 +86,7 @@ Path M. "sortText": "b", "insertText": "->M.b", "additionalTextEdits": [{ - "range": {"start": {"line": 17, "character": 19}, "end": {"line": 17, "character": 20}}, + "range": {"start": {"line": 16, "character": 19}, "end": {"line": 16, "character": 20}}, "newText": "" }] }, { @@ -119,7 +98,7 @@ Path M. "sortText": "a", "insertText": "->M.a", "additionalTextEdits": [{ - "range": {"start": {"line": 17, "character": 19}, "end": {"line": 17, "character": 20}}, + "range": {"start": {"line": 16, "character": 19}, "end": {"line": 16, "character": 20}}, "newText": "" }] }] From d1c3eb2be21d445dd1f7c50087d9d9a2ddc4e7c8 Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 20 Feb 2025 21:13:07 +0100 Subject: [PATCH 5/6] Optional dig --- analysis/src/CompletionBackEnd.ml | 7 +------ analysis/src/TypeUtils.ml | 12 +++++++++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 41c74b5284..c7e5713123 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -966,11 +966,7 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact (* should not happen, but just ignore extra arguments *) [] in - (* (match typ.desc with - | Tarrow _ -> print_endline "its an arrow" - | Tconstr _ -> print_endline "it's an alias" - | _ -> print_endline "something else"); *) - match TypeUtils.extractFunctionType ~env ~package typ with + match TypeUtils.extractFunctionType ~env ~package ~digInto:false typ with | args, tRet when args <> [] -> let args = processApply args labels in let retType = reconstructFunctionType args tRet in @@ -1052,7 +1048,6 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact | None -> []) | None -> []) | CPPipe {contextPath = cp; id = prefix; lhsLoc; inJsx; synthetic} -> ( - (* TODO: resolve somewhere in here a potential alias *) if Debug.verbose () then print_endline "[ctx_path]--> CPPipe"; match cp diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 33e10a3c53..c9b1acd011 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -243,11 +243,21 @@ let rec extractObjectType ~env ~package (t : Types.type_expr) = | _ -> None) | _ -> None -let extractFunctionType ~env ~package typ = +let extractFunctionType ~env ~package ?(digInto = true) typ = let rec loop ~env acc (t : Types.type_expr) = match t.desc with | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> loop ~env acc t1 | Tarrow (label, tArg, tRet, _, _) -> loop ~env ((label, tArg) :: acc) tRet + | Tconstr (path, typeArgs, _) when digInto -> ( + match References.digConstructor ~env ~package path with + | Some + ( env, + { + item = {decl = {type_manifest = Some t1; type_params = typeParams}}; + } ) -> + let t1 = t1 |> instantiateType ~typeParams ~typeArgs in + loop ~env acc t1 + | _ -> (List.rev acc, t)) | _ -> (List.rev acc, t) in loop ~env [] typ From 57142d54f974d77f3822ef151d2bf2877846a7f0 Mon Sep 17 00:00:00 2001 From: nojaf Date: Thu, 20 Feb 2025 21:14:36 +0100 Subject: [PATCH 6/6] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7a4e157a2..67dcfc7a42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - Stdlib namespace for Core modules (fixes name clashes with user modules). https://github.com/rescript-lang/rescript/pull/7285 - Fix runtime type check for Object in untagged variants when one variant case is `null`. https://github.com/rescript-lang/rescript/pull/7303 - Fix files that were being truncated when sent to the CDN over FTP. https://github.com/rescript-lang/rescript/pull/7306 +- Fix better editor completion for applications. https://github.com/rescript-lang/rescript/pull/7291 #### :house: Internal