diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b5aa429d..9e2a0b4bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Add support for syntax highlighting in `%raw` and `%ffi` extension points. https://github.com/rescript-lang/rescript-vscode/pull/774 - Add completion to top level decorators. https://github.com/rescript-lang/rescript-vscode/pull/799 - Add code action for wrapping patterns where option is expected with `Some`. https://github.com/rescript-lang/rescript-vscode/pull/806 +- Better completion from identifiers with inferred types. https://github.com/rescript-lang/rescript-vscode/pull/808 #### :nail_care: Polish diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index a510fb15a..3ca0b904c 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1113,6 +1113,14 @@ and getCompletionsForContextPath ~debug ~full ~opens ~rawOpens ~pos ~env ~exact [Completion.create "dummy" ~env ~kind:(kindFromInnerType typ)] | None -> []) | None -> []) + | CTypeAtPos loc -> ( + match + References.getLocItem ~full ~pos:(Pos.ofLexing loc.loc_start) ~debug + with + | None -> [] + | Some {locType = Typed (_, typExpr, _)} -> + [Completion.create "dummy" ~env ~kind:(Value typExpr)] + | _ -> []) let getOpens ~debug ~rawOpens ~package ~env = if debug && rawOpens <> [] then diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index 470becc5e..797f28248 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -591,6 +591,27 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = (Completable.Cexpression {contextPath = ctxPath; prefix; nested = List.rev nested}) | _ -> ()) + | {pvb_pat = {ppat_desc = Ppat_var {loc}}; pvb_expr} + when locHasCursor pvb_expr.pexp_loc -> ( + (* Expression without a type annotation. We can complete this if this + has compiled previously and there's a type available for the identifier itself. + This is nice because the type is assigned even if the assignment isn't complete. + + E.g: let x = {name: "name", }, when `x` has compiled. *) + match + pvb_expr + |> CompletionExpressions.traverseExpr ~exprPath:[] ~pos:posBeforeCursor + ~firstCharBeforeCursorNoWhite + with + | Some (prefix, nested) -> + (* This completion should be low prio, so let any deeper completion + hit first, and only set this TypeAtPos completion if nothing else + here hit. *) + Ast_iterator.default_iterator.value_binding iterator value_binding; + setResult + (Completable.Cexpression + {contextPath = CTypeAtPos loc; prefix; nested = List.rev nested}) + | _ -> ()) | { pvb_pat = {ppat_desc = Ppat_constraint (_pat, coreType); ppat_loc}; pvb_expr; diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index e12223cdb..5af4b2b74 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -592,6 +592,8 @@ module Completable = struct } | CJsxPropValue of {pathToComponent: string list; propName: string} | CPatternPath of {rootCtxPath: contextPath; nested: nestedPath list} + | CTypeAtPos of Location.t + (** A position holding something that might have a *compiled* type. *) type patternMode = Default | Destructuring @@ -671,6 +673,7 @@ module Completable = struct ^ (nested |> List.map (fun nestedPath -> nestedPathToString nestedPath) |> String.concat "->") + | CTypeAtPos _loc -> "CTypeAtPos()" let toString = function | Cpath cp -> "Cpath " ^ contextPathToString cp diff --git a/analysis/tests/src/TypeAtPosCompletion.res b/analysis/tests/src/TypeAtPosCompletion.res new file mode 100644 index 000000000..41d558bed --- /dev/null +++ b/analysis/tests/src/TypeAtPosCompletion.res @@ -0,0 +1,25 @@ +type optRecord = { + name: string, + age?: int, + online?: bool, +} + +let optRecord = { + name: "Hello", + // ^com +} + +type someVariant = One(int, optRecord) + +let x = One( + 1, + { + name: "What", + // ^com + }, +) + +let arr = [ + optRecord, + // ^com +] diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index 560385753..aef3b28be 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -1764,6 +1764,12 @@ Path T "tags": [], "detail": "file module", "documentation": null + }, { + "label": "TypeAtPosCompletion", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null }, { "label": "TypeDefinition", "kind": 9, diff --git a/analysis/tests/src/expected/CompletionJsxProps.res.txt b/analysis/tests/src/expected/CompletionJsxProps.res.txt index 475fbb2cf..43a91f799 100644 --- a/analysis/tests/src/expected/CompletionJsxProps.res.txt +++ b/analysis/tests/src/expected/CompletionJsxProps.res.txt @@ -68,6 +68,12 @@ Path CompletionSupport.TestComponent.make "tags": [], "detail": "file module", "documentation": null + }, { + "label": "TypeAtPosCompletion", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null }, { "label": "TypeDefinition", "kind": 9, diff --git a/analysis/tests/src/expected/Fragment.res.txt b/analysis/tests/src/expected/Fragment.res.txt index d80d1c56a..adb67f8d8 100644 --- a/analysis/tests/src/expected/Fragment.res.txt +++ b/analysis/tests/src/expected/Fragment.res.txt @@ -12,5 +12,9 @@ posCursor:[9:56] posNoWhite:[9:55] Found expr:[9:13->9:66] JSX 9:26] > _children:9:26 posCursor:[9:56] posNoWhite:[9:55] Found expr:__ghost__[9:10->9:67] Pexp_construct []:__ghost__[9:10->9:67] None +Completable: Cexpression CTypeAtPos()=[]->variantPayload::::($1) +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath CTypeAtPos() null diff --git a/analysis/tests/src/expected/TypeAtPosCompletion.res.txt b/analysis/tests/src/expected/TypeAtPosCompletion.res.txt new file mode 100644 index 000000000..8a334e9c6 --- /dev/null +++ b/analysis/tests/src/expected/TypeAtPosCompletion.res.txt @@ -0,0 +1,60 @@ +Complete src/TypeAtPosCompletion.res 7:17 +posCursor:[7:17] posNoWhite:[7:15] Found expr:[6:16->9:1] +Completable: Cexpression CTypeAtPos()->recordBody +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath CTypeAtPos() +[{ + "label": "age", + "kind": 5, + "tags": [], + "detail": "age?: int\n\noptRecord", + "documentation": null + }, { + "label": "online", + "kind": 5, + "tags": [], + "detail": "online?: bool\n\noptRecord", + "documentation": null + }] + +Complete src/TypeAtPosCompletion.res 16:18 +posCursor:[16:18] posNoWhite:[16:16] Found expr:[13:8->19:1] +Pexp_construct One:[13:8->13:11] [13:11->19:1] +posCursor:[16:18] posNoWhite:[16:16] Found expr:[13:11->19:1] +posCursor:[16:18] posNoWhite:[16:16] Found expr:[15:2->18:3] +Completable: Cexpression CTypeAtPos()->variantPayload::One($1), recordBody +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath CTypeAtPos() +[{ + "label": "age", + "kind": 5, + "tags": [], + "detail": "age?: int\n\noptRecord", + "documentation": null + }, { + "label": "online", + "kind": 5, + "tags": [], + "detail": "online?: bool\n\noptRecord", + "documentation": null + }] + +Complete src/TypeAtPosCompletion.res 22:12 +posCursor:[22:12] posNoWhite:[22:11] Found expr:[21:10->24:1] +Completable: Cexpression CTypeAtPos()->array +Package opens Pervasives.JsxModules.place holder +Resolved opens 1 pervasives +ContextPath CTypeAtPos() +[{ + "label": "{}", + "kind": 12, + "tags": [], + "detail": "optRecord", + "documentation": null, + "sortText": "A", + "insertText": "{$0}", + "insertTextFormat": 2 + }] +