Skip to content

[WIP] Annotated completions #711

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jan 24, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
- Support inline records in completion. https://github.com/rescript-lang/rescript-vscode/pull/695
- Add way to autocomplete an exhaustive switch statement for identifiers. Example: an identifier that's a variant can have a switch autoinserted matching all variant cases. https://github.com/rescript-lang/rescript-vscode/pull/699
- Support typed expression completion for lowercase (builtin) JSX tags. https://github.com/rescript-lang/rescript-vscode/pull/702
- Support typed expression completion driven by type annotations. https://github.com/rescript-lang/rescript-vscode/pull/711

#### :nail_care: Polish

Expand Down
143 changes: 104 additions & 39 deletions analysis/src/CompletionBackEnd.ml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ let detail name (kind : Completion.kind) =
^ ")")
^ "\n\n" ^ s
| Snippet s -> s
| ExtractedType (extractedType, _) ->
TypeUtils.extractedTypeToString extractedType

let findAllCompletions ~(env : QueryEnv.t) ~prefix ~exact ~namesUsed
~(completionContext : Completable.completionContext) =
Expand Down Expand Up @@ -582,8 +584,24 @@ let completionsGetTypeEnv = function
| {Completion.kind = Field ({typ}, _); env} :: _ -> Some (typ, env)
| _ -> None

let completionsGetCompletionType ~full = function
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every case seems to return the same env. Factor out?

| {Completion.kind = Value typ; env} :: _
| {Completion.kind = ObjLabel typ; env} :: _
| {Completion.kind = Field ({typ}, _); env} :: _ ->
typ
|> TypeUtils.extractType ~env ~package:full.package
|> Option.map (fun typ -> (typ, env))
| {Completion.kind = Type typ; env} :: _ -> (
match TypeUtils.extractTypeFromResolvedType typ ~env ~full with
| None -> None
| Some extractedType -> Some (extractedType, env))
| {Completion.kind = ExtractedType (typ, _); env} :: _ -> Some (typ, env)
| _ -> None

type getCompletionsForContextPathMode = Regular | Pipe

let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
~exact ~scope (contextPath : Completable.contextPath) =
~exact ~scope ?(mode = Regular) (contextPath : Completable.contextPath) =
let package = full.package in
match contextPath with
| CPString ->
Expand All @@ -607,13 +625,50 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
(Completion.Value
(Ctype.newconstr (Path.Pident (Ident.create "float")) []));
]
| CPArray ->
| CPArray None ->
[
Completion.create "array" ~env
~kind:
(Completion.Value
(Ctype.newconstr (Path.Pident (Ident.create "array")) []));
]
| CPArray (Some cp) -> (
match mode with
| Regular -> (
match
cp
|> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos
~env ~exact:true ~scope
|> completionsGetCompletionType ~full
with
| None -> []
| Some (typ, env) ->
[
Completion.create "dummy" ~env
~kind:(Completion.ExtractedType (Tarray (env, typ), `Type));
])
| Pipe ->
(* Pipe completion with array just needs to know that it's an array, not
what inner type it has. *)
[
Completion.create "array" ~env
~kind:
(Completion.Value
(Ctype.newconstr (Path.Pident (Ident.create "array")) []));
])
| CPOption cp -> (
match
cp
|> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
~exact:true ~scope
|> completionsGetCompletionType ~full
with
| None -> []
| Some (typ, env) ->
[
Completion.create "dummy" ~env
~kind:(Completion.ExtractedType (Toption (env, typ), `Type));
])
| CPId (path, completionContext) ->
path
|> getCompletionsForPath ~package ~opens ~allFiles ~pos ~exact
Expand Down Expand Up @@ -719,7 +774,7 @@ let rec getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
match
cp
|> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
~exact:true ~scope
~exact:true ~scope ~mode:Pipe
|> completionsGetTypeEnv
with
| None -> []
Expand Down Expand Up @@ -994,21 +1049,16 @@ let printConstructorArgs argsLen ~asSnippet =

type completionMode = Pattern | Expression

let rec completeTypedValue (t : SharedTypes.completionType) ~env ~full ~prefix
let rec completeTypedValue (t : SharedTypes.completionType) ~full ~prefix
~completionContext ~mode =
let extractedType =
match t with
| TypeExpr t -> t |> TypeUtils.extractType ~env ~package:full.package
| InlineRecord fields -> Some (TinlineRecord {env; fields})
in
match extractedType with
| Some (Tbool env) ->
match t with
| Tbool env ->
[
Completion.create "true" ~kind:(Label "bool") ~env;
Completion.create "false" ~kind:(Label "bool") ~env;
]
|> filterItems ~prefix
| Some (Tvariant {env; constructors; variantDecl; variantName}) ->
| Tvariant {env; constructors; variantDecl; variantName} ->
constructors
|> List.map (fun (constructor : Constructor.t) ->
let numArgs =
Expand All @@ -1028,7 +1078,7 @@ let rec completeTypedValue (t : SharedTypes.completionType) ~env ~full ~prefix
(constructor, variantDecl |> Shared.declToString variantName))
~env ())
|> filterItems ~prefix
| Some (Tpolyvariant {env; constructors; typeExpr}) ->
| Tpolyvariant {env; constructors; typeExpr} ->
constructors
|> List.map (fun (constructor : polyVariantConstructor) ->
Completion.createWithSnippet
Expand All @@ -1048,11 +1098,11 @@ let rec completeTypedValue (t : SharedTypes.completionType) ~env ~full ~prefix
(constructor, typeExpr |> Shared.typeToString))
~env ())
|> filterItems ~prefix
| Some (Toption (env, t)) ->
let innerType = Utils.unwrapIfOption t in
| Toption (env, t) ->
let innerType = TypeUtils.unwrapCompletionTypeIfOption t in
let expandedCompletions =
TypeExpr innerType
|> completeTypedValue ~env ~full ~prefix ~completionContext ~mode
innerType
|> completeTypedValue ~full ~prefix ~completionContext ~mode
|> List.map (fun (c : Completion.t) ->
{
c with
Expand All @@ -1065,22 +1115,24 @@ let rec completeTypedValue (t : SharedTypes.completionType) ~env ~full ~prefix
})
in
[
Completion.create "None" ~kind:(Label (t |> Shared.typeToString)) ~env;
Completion.create "None"
~kind:(Label (t |> TypeUtils.extractedTypeToString))
~env;
Completion.createWithSnippet ~name:"Some(_)"
~kind:(Label (t |> Shared.typeToString))
~kind:(Label (t |> TypeUtils.extractedTypeToString))
~env ~insertText:"Some(${1:_})" ();
]
@ expandedCompletions
|> filterItems ~prefix
| Some (Tuple (env, exprs, typ)) ->
| Tuple (env, exprs, typ) ->
let numExprs = List.length exprs in
[
Completion.createWithSnippet
~name:(printConstructorArgs numExprs ~asSnippet:false)
~insertText:(printConstructorArgs numExprs ~asSnippet:true)
~kind:(Value typ) ~env ();
]
| Some (Trecord {env; fields; typeExpr}) -> (
| Trecord {env; fields} as extractedType -> (
(* As we're completing for a record, we'll need a hint (completionContext)
here to figure out whether we should complete for a record field, or
the record body itself. *)
Expand All @@ -1091,18 +1143,26 @@ let rec completeTypedValue (t : SharedTypes.completionType) ~env ~full ~prefix
List.mem field.fname.txt seenFields = false)
|> List.map (fun (field : field) ->
Completion.create field.fname.txt
~kind:(Field (field, typeExpr |> Shared.typeToString))
~kind:
(Field (field, TypeUtils.extractedTypeToString extractedType))
~env)
|> filterItems ~prefix
| None ->
if prefix = "" then
[
Completion.createWithSnippet ~name:"{}"
~insertText:(if !Cfg.supportsSnippets then "{$0}" else "{}")
~sortText:"A" ~kind:(Value typeExpr) ~env ();
~sortText:"A"
~kind:
(ExtractedType
( extractedType,
match mode with
| Pattern -> `Type
| Expression -> `Value ))
~env ();
]
else [])
| Some (TinlineRecord {env; fields}) -> (
| TinlineRecord {env; fields} -> (
match completionContext with
| Some (Completable.RecordField {seenFields}) ->
fields
Expand All @@ -1120,15 +1180,22 @@ let rec completeTypedValue (t : SharedTypes.completionType) ~env ~full ~prefix
~sortText:"A" ~kind:(Label "Inline record") ~env ();
]
else [])
| Some (Tarray (env, typeExpr)) ->
| Tarray (env, typ) ->
if prefix = "" then
[
Completion.createWithSnippet ~name:"[]"
~insertText:(if !Cfg.supportsSnippets then "[$0]" else "[]")
~sortText:"A" ~kind:(Value typeExpr) ~env ();
~sortText:"A"
~kind:
(ExtractedType
( typ,
match mode with
| Pattern -> `Type
| Expression -> `Value ))
~env ();
]
else []
| Some (Tstring env) ->
| Tstring env ->
if prefix = "" then
[
Completion.createWithSnippet ~name:"\"\""
Expand All @@ -1139,7 +1206,7 @@ let rec completeTypedValue (t : SharedTypes.completionType) ~env ~full ~prefix
~env ();
]
else []
| Some (Tfunction {env; typ; args}) when prefix = "" && mode = Expression ->
| Tfunction {env; typ; args} when prefix = "" && mode = Expression ->
let prettyPrintArgTyp ?currentIndex (argTyp : Types.type_expr) =
let indexText =
match currentIndex with
Expand Down Expand Up @@ -1309,15 +1376,16 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover
with
| Some (typ, env) -> (
match
TypeExpr typ
|> TypeUtils.resolveNested ~env ~package:full.package ~nested
typ
|> TypeUtils.extractType ~env ~package:full.package
|> Utils.Option.flatMap (fun typ ->
typ |> TypeUtils.resolveNested ~env ~full ~nested)
with
| None -> fallbackOrEmpty ()
| Some (typ, env, completionContext) ->
| Some (typ, _env, completionContext) ->
let items =
typ
|> completeTypedValue ~mode:Pattern ~env ~full ~prefix
~completionContext
|> completeTypedValue ~mode:Pattern ~full ~prefix ~completionContext
in
fallbackOrEmpty ~items ())
| None -> fallbackOrEmpty ())
Expand All @@ -1326,14 +1394,11 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover
contextPath
|> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env
~exact:true ~scope
|> completionsGetTypeEnv
|> completionsGetCompletionType ~full
with
| None -> []
| Some (typ, env) -> (
match
TypeExpr typ
|> TypeUtils.resolveNested ~env ~package:full.package ~nested
with
match typ |> TypeUtils.resolveNested ~env ~full ~nested with
| None -> []
| Some (typ, env, completionContext) -> (
let isJsx =
Expand All @@ -1343,7 +1408,7 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover
in
let items =
typ
|> completeTypedValue ~mode:Expression ~env ~full ~prefix
|> completeTypedValue ~mode:Expression ~full ~prefix
~completionContext
|> List.map (fun (c : Completion.t) ->
if isJsx then
Expand Down
81 changes: 59 additions & 22 deletions analysis/src/CompletionFrontEnd.ml
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,12 @@ let rec exprToContextPath (e : Parsetree.expression) =
| Pexp_constant (Pconst_string _) -> Some Completable.CPString
| Pexp_constant (Pconst_integer _) -> Some CPInt
| Pexp_constant (Pconst_float _) -> Some CPFloat
| Pexp_array _ -> Some CPArray
| Pexp_array exprs ->
Some
(CPArray
(match exprs with
| [] -> None
| exp :: _ -> exprToContextPath exp))
| Pexp_ident {txt} -> Some (CPId (Utils.flattenLongIdent txt, Value))
| Pexp_field (e1, {txt = Lident name}) -> (
match exprToContextPath e1 with
Expand Down Expand Up @@ -316,27 +321,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
| _ -> ()
in
let scopeValueBinding (vb : Parsetree.value_binding) =
scopePattern vb.pvb_pat;
(* Identify relevant destructures for completion, like `let {<com>} = someVar` or `let (true, false) = someFn()`. *)
match vb with
| {pvb_pat; pvb_expr} when locHasCursor pvb_pat.ppat_loc -> (
match
( pvb_pat
|> CompletionPatterns.traversePattern ~patternPath:[] ~locHasCursor
~firstCharBeforeCursorNoWhite ~posBeforeCursor,
exprToContextPath pvb_expr )
with
| Some (prefix, nestedPattern), Some ctxPath ->
setResult
(Completable.Cpattern
{
contextPath = ctxPath;
prefix;
nested = List.rev nestedPattern;
fallback = None;
})
| _ -> ())
| _ -> ()
scopePattern vb.pvb_pat
in
let scopeTypeKind (tk : Parsetree.type_kind) =
match tk with
Expand Down Expand Up @@ -490,6 +475,58 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
(value_binding : Parsetree.value_binding) =
let oldInJsxContext = !inJsxContext in
if Utils.isReactComponent value_binding then inJsxContext := true;
(match value_binding with
| {pvb_pat = {ppat_desc = Ppat_constraint (_pat, coreType)}; pvb_expr}
when locHasCursor pvb_expr.pexp_loc -> (
(* Expression with derivable type annotation.
E.g: let x: someRecord = {<com>} *)
match
( TypeUtils.contextPathFromCoreType coreType,
pvb_expr
|> CompletionExpressions.traverseExpr ~exprPath:[]
~pos:posBeforeCursor ~firstCharBeforeCursorNoWhite )
with
| Some ctxPath, Some (prefix, nested) ->
setResult
(Completable.Cexpression
{contextPath = ctxPath; prefix; nested = List.rev nested})
| _ -> ())
| {
pvb_pat = {ppat_desc = Ppat_constraint (_pat, coreType); ppat_loc};
pvb_expr;
}
when locHasCursor value_binding.pvb_loc
&& locHasCursor ppat_loc = false
&& locHasCursor pvb_expr.pexp_loc = false
&& CompletionExpressions.isExprHole pvb_expr -> (
(* Expression with derivable type annotation, when the expression is empty (expr hole).
E.g: let x: someRecord = <com> *)
match TypeUtils.contextPathFromCoreType coreType with
| Some ctxPath ->
setResult
(Completable.Cexpression
{contextPath = ctxPath; prefix = ""; nested = []})
| _ -> ())
| {pvb_pat; pvb_expr} when locHasCursor pvb_pat.ppat_loc -> (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it clear that this case cannot be an instance of the cases above? Are we relying on the order of cases?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fairly sure we're not depending on the ordering, but we're getting dangerously close...

(* Completing a destructuring.
E.g: let {<com>} = someVar *)
match
( pvb_pat
|> CompletionPatterns.traversePattern ~patternPath:[] ~locHasCursor
~firstCharBeforeCursorNoWhite ~posBeforeCursor,
exprToContextPath pvb_expr )
with
| Some (prefix, nested), Some ctxPath ->
setResult
(Completable.Cpattern
{
contextPath = ctxPath;
prefix;
nested = List.rev nested;
fallback = None;
})
| _ -> ())
| _ -> ());
Ast_iterator.default_iterator.value_binding iterator value_binding;
inJsxContext := oldInJsxContext
in
Expand Down
Loading