diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a9ea2a50..2bcd18904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index f503721cb..75b25c138 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -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) = @@ -582,8 +584,24 @@ let completionsGetTypeEnv = function | {Completion.kind = Field ({typ}, _); env} :: _ -> Some (typ, env) | _ -> None +let completionsGetCompletionType ~full = function + | {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 -> @@ -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 @@ -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 -> [] @@ -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 = @@ -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 @@ -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 @@ -1065,14 +1115,16 @@ 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 @@ -1080,7 +1132,7 @@ let rec completeTypedValue (t : SharedTypes.completionType) ~env ~full ~prefix ~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. *) @@ -1091,7 +1143,8 @@ 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 -> @@ -1099,10 +1152,17 @@ let rec completeTypedValue (t : SharedTypes.completionType) ~env ~full ~prefix [ 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 @@ -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:"\"\"" @@ -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 @@ -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 ()) @@ -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 = @@ -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 diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index 0d28404c9..9fe9c5369 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -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 @@ -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 {} = 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 @@ -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 = {} *) + 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 = *) + match TypeUtils.contextPathFromCoreType coreType with + | Some ctxPath -> + setResult + (Completable.Cexpression + {contextPath = ctxPath; prefix = ""; nested = []}) + | _ -> ()) + | {pvb_pat; pvb_expr} when locHasCursor pvb_pat.ppat_loc -> ( + (* Completing a destructuring. + E.g: let {} = 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 diff --git a/analysis/src/DumpAst.ml b/analysis/src/DumpAst.ml index cb651dbce..dc841b41a 100644 --- a/analysis/src/DumpAst.ml +++ b/analysis/src/DumpAst.ml @@ -59,10 +59,10 @@ let printCoreType typ ~pos = match typ.ptyp_desc with | Ptyp_any -> "Ptyp_any" | Ptyp_var name -> "Ptyp_var(" ^ str name ^ ")" - | Ptyp_constr (loc, _types) -> + | Ptyp_constr (lid, _types) -> "Ptyp_constr(" - ^ (loc |> printLocDenominatorLoc ~pos) - ^ (Utils.flattenLongIdent loc.txt |> ident |> str) + ^ (lid |> printLocDenominatorLoc ~pos) + ^ (Utils.flattenLongIdent lid.txt |> ident |> str) ^ ")" | Ptyp_variant _ -> "Ptyp_variant()" | _ -> "" diff --git a/analysis/src/ProcessCmt.ml b/analysis/src/ProcessCmt.ml index 2865dc02d..67b910830 100644 --- a/analysis/src/ProcessCmt.ml +++ b/analysis/src/ProcessCmt.ml @@ -64,6 +64,7 @@ let rec forTypeSignatureItem ~(env : SharedTypes.Env.t) ~(exported : Exported.t) ~item: { Type.decl; + name = name.txt; kind = (match type_kind with | Type_abstract -> ( @@ -167,6 +168,7 @@ let forTypeDeclaration ~env ~(exported : Exported.t) ~item: { Type.decl = typ_type; + name = name.txt; kind = (match typ_kind with | Ttype_abstract -> ( diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index 638246fb4..b8f811fad 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -34,8 +34,6 @@ type field = { docstring: string list; } -type completionType = TypeExpr of Types.type_expr | InlineRecord of field list - type constructorArgs = | InlineRecord of field list | Args of (Types.type_expr * Location.t) list @@ -59,7 +57,7 @@ module Type = struct | Record of field list | Variant of Constructor.t list - type t = {kind: kind; decl: Types.type_declaration} + type t = {kind: kind; decl: Types.type_declaration; name: string} end module Exported = struct @@ -296,6 +294,36 @@ end type polyVariantConstructor = {name: string; args: Types.type_expr list} +(** An type that can be used to drive completion *) +type completionType = + | Tuple of QueryEnv.t * Types.type_expr list * Types.type_expr + | Toption of QueryEnv.t * completionType + | Tbool of QueryEnv.t + | Tarray of QueryEnv.t * completionType + | Tstring of QueryEnv.t + | Tvariant of { + env: QueryEnv.t; + constructors: Constructor.t list; + variantDecl: Types.type_declaration; + variantName: string; + } + | Tpolyvariant of { + env: QueryEnv.t; + constructors: polyVariantConstructor list; + typeExpr: Types.type_expr; + } + | Trecord of { + env: QueryEnv.t; + fields: field list; + definition: + [ `NameOnly of string + (** When we only have the name, like when pulling the record from a declared type. *) + | `TypeExpr of Types.type_expr + (** When we have the full type expr from the compiler. *) ]; + } + | TinlineRecord of {env: QueryEnv.t; fields: field list} + | Tfunction of {env: QueryEnv.t; args: typedFnArg list; typ: Types.type_expr} + module Completion = struct type kind = | Module of Module.t @@ -308,6 +336,7 @@ module Completion = struct | Field of field * string | FileModule of string | Snippet of string + | ExtractedType of completionType * [`Value | `Type] type t = { name: string; @@ -358,8 +387,8 @@ module Completion = struct | ObjLabel _ -> 4 | Label _ -> 4 | Field (_, _) -> 5 - | Type _ -> 22 - | Value _ -> 12 + | Type _ | ExtractedType (_, `Type) -> 22 + | Value _ | ExtractedType (_, `Value) -> 12 | Snippet _ -> 15 end @@ -557,9 +586,10 @@ module Completable = struct type contextPath = | CPString - | CPArray + | CPArray of contextPath option | CPInt | CPFloat + | CPOption of contextPath | CPApply of contextPath * Asttypes.arg_label list | CPId of string list * completionContext | CPField of contextPath * string @@ -622,36 +652,6 @@ module Completable = struct } | CexhaustiveSwitch of {contextPath: contextPath; exprLoc: Location.t} - (** An extracted type from a type expr *) - type extractedType = - | Tuple of QueryEnv.t * Types.type_expr list * Types.type_expr - | Toption of QueryEnv.t * Types.type_expr - | Tbool of QueryEnv.t - | Tarray of QueryEnv.t * Types.type_expr - | Tstring of QueryEnv.t - | Tvariant of { - env: QueryEnv.t; - constructors: Constructor.t list; - variantDecl: Types.type_declaration; - variantName: string; - } - | Tpolyvariant of { - env: QueryEnv.t; - constructors: polyVariantConstructor list; - typeExpr: Types.type_expr; - } - | Trecord of { - env: QueryEnv.t; - fields: field list; - typeExpr: Types.type_expr; - } - | TinlineRecord of {env: QueryEnv.t; fields: field list} - | Tfunction of { - env: QueryEnv.t; - args: typedFnArg list; - typ: Types.type_expr; - } - let toString = let completionContextToString = function | Value -> "Value" @@ -663,6 +663,7 @@ module Completable = struct | CPString -> "string" | CPInt -> "int" | CPFloat -> "float" + | CPOption ctxPath -> "option<" ^ contextPathToString ctxPath ^ ">" | CPApply (cp, labels) -> contextPathToString cp ^ "(" ^ (labels @@ -672,7 +673,8 @@ module Completable = struct | Optional s -> "?" ^ s) |> String.concat ", ") ^ ")" - | CPArray -> "array" + | CPArray (Some ctxPath) -> "array<" ^ contextPathToString ctxPath ^ ">" + | CPArray None -> "array" | CPId (sl, completionContext) -> completionContextToString completionContext ^ list sl | CPField (cp, s) -> contextPathToString cp ^ "." ^ str s diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index 3535efccc..a07490b8c 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -112,9 +112,11 @@ let rec extractType ~env ~package (t : Types.type_expr) = match t.desc with | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> extractType ~env ~package t1 | Tconstr (Path.Pident {name = "option"}, [payloadTypeExpr], _) -> - Some (Completable.Toption (env, payloadTypeExpr)) + payloadTypeExpr |> extractType ~env ~package + |> Option.map (fun payloadTyp -> Toption (env, payloadTyp)) | Tconstr (Path.Pident {name = "array"}, [payloadTypeExpr], _) -> - Some (Tarray (env, payloadTypeExpr)) + payloadTypeExpr |> extractType ~env ~package + |> Option.map (fun payloadTyp -> Tarray (env, payloadTyp)) | Tconstr (Path.Pident {name = "bool"}, [], _) -> Some (Tbool env) | Tconstr (Path.Pident {name = "string"}, [], _) -> Some (Tstring env) | Tconstr (path, _, _) -> ( @@ -126,7 +128,7 @@ let rec extractType ~env ~package (t : Types.type_expr) = (Tvariant {env; constructors; variantName = name.txt; variantDecl = decl}) | Some (env, {item = {kind = Record fields}}) -> - Some (Trecord {env; fields; typeExpr = t}) + Some (Trecord {env; fields; definition = `TypeExpr t}) | _ -> None) | Ttuple expressions -> Some (Tuple (env, expressions, t)) | Tvariant {row_fields} -> @@ -242,23 +244,36 @@ let rec resolveTypeForPipeCompletion ~env ~package ~lhsLoc ~full in digToRelevantType ~env ~package t) +let extractTypeFromResolvedType (typ : Type.t) ~env ~full = + match typ.kind with + | Tuple items -> Some (Tuple (env, items, Ctype.newty (Ttuple items))) + | Record fields -> + Some (Trecord {env; fields; definition = `NameOnly typ.name}) + | Variant constructors -> + Some + (Tvariant + {env; constructors; variantName = typ.name; variantDecl = typ.decl}) + | Abstract _ | Open -> ( + match typ.decl.type_manifest with + | None -> None + | Some t -> t |> extractType ~env ~package:full.package) + (** This moves through a nested path via a set of instructions, trying to resolve the type at the end of the path. *) -let rec resolveNested (typ : completionType) ~env ~package ~nested = +let rec resolveNested (typ : completionType) ~env ~full ~nested = match nested with | [] -> Some (typ, env, None) | patternPath :: nested -> ( - let extractedType = - match typ with - | TypeExpr typ -> typ |> extractType ~env ~package - | InlineRecord fields -> Some (TinlineRecord {env; fields}) - in - match (patternPath, extractedType) with - | Completable.NTupleItem {itemNum}, Some (Tuple (env, tupleItems, _)) -> ( + match (patternPath, typ) with + | Completable.NTupleItem {itemNum}, Tuple (env, tupleItems, _) -> ( match List.nth_opt tupleItems itemNum with | None -> None - | Some typ -> TypeExpr typ |> resolveNested ~env ~package ~nested) + | Some typ -> + typ + |> extractType ~env ~package:full.package + |> Utils.Option.flatMap (fun typ -> + typ |> resolveNested ~env ~full ~nested)) | ( NFollowRecordField {fieldName}, - Some (TinlineRecord {env; fields} | Trecord {env; fields}) ) -> ( + (TinlineRecord {env; fields} | Trecord {env; fields}) ) -> ( match fields |> List.find_opt (fun (field : field) -> field.fname.txt = fieldName) @@ -266,17 +281,29 @@ let rec resolveNested (typ : completionType) ~env ~package ~nested = | None -> None | Some {typ; optional} -> let typ = if optional then Utils.unwrapIfOption typ else typ in - TypeExpr typ |> resolveNested ~env ~package ~nested) - | NRecordBody {seenFields}, Some (Trecord {env; typeExpr}) -> - Some (TypeExpr typeExpr, env, Some (Completable.RecordField {seenFields})) - | NRecordBody {seenFields}, Some (TinlineRecord {env; fields}) -> + typ + |> extractType ~env ~package:full.package + |> Utils.Option.flatMap (fun typ -> + typ |> resolveNested ~env ~full ~nested)) + | NRecordBody {seenFields}, Trecord {env; definition = `TypeExpr typeExpr} + -> + typeExpr + |> extractType ~env ~package:full.package + |> Option.map (fun typ -> + (typ, env, Some (Completable.RecordField {seenFields}))) + | ( NRecordBody {seenFields}, + (Trecord {env; definition = `NameOnly _} as extractedType) ) -> + Some (extractedType, env, Some (Completable.RecordField {seenFields})) + | NRecordBody {seenFields}, TinlineRecord {env; fields} -> Some - (InlineRecord fields, env, Some (Completable.RecordField {seenFields})) - | ( NVariantPayload {constructorName = "Some"; itemNum = 0}, - Some (Toption (env, typ)) ) -> - TypeExpr typ |> resolveNested ~env ~package ~nested - | ( NVariantPayload {constructorName; itemNum}, - Some (Tvariant {env; constructors}) ) -> ( + ( TinlineRecord {fields; env}, + env, + Some (Completable.RecordField {seenFields}) ) + | NVariantPayload {constructorName = "Some"; itemNum = 0}, Toption (env, typ) + -> + typ |> resolveNested ~env ~full ~nested + | NVariantPayload {constructorName; itemNum}, Tvariant {env; constructors} + -> ( match constructors |> List.find_opt (fun (c : Constructor.t) -> @@ -285,12 +312,16 @@ let rec resolveNested (typ : completionType) ~env ~package ~nested = | Some {args = Args args} -> ( match List.nth_opt args itemNum with | None -> None - | Some (typ, _) -> TypeExpr typ |> resolveNested ~env ~package ~nested) + | Some (typ, _) -> + typ + |> extractType ~env ~package:full.package + |> Utils.Option.flatMap (fun typ -> + typ |> resolveNested ~env ~full ~nested)) | Some {args = InlineRecord fields} when itemNum = 0 -> - InlineRecord fields |> resolveNested ~env ~package ~nested + TinlineRecord {env; fields} |> resolveNested ~env ~full ~nested | _ -> None) | ( NPolyvariantPayload {constructorName; itemNum}, - Some (Tpolyvariant {env; constructors}) ) -> ( + Tpolyvariant {env; constructors} ) -> ( match constructors |> List.find_opt (fun (c : polyVariantConstructor) -> @@ -300,9 +331,12 @@ let rec resolveNested (typ : completionType) ~env ~package ~nested = | Some constructor -> ( match List.nth_opt constructor.args itemNum with | None -> None - | Some typ -> TypeExpr typ |> resolveNested ~env ~package ~nested)) - | NArray, Some (Tarray (env, typ)) -> - TypeExpr typ |> resolveNested ~env ~package ~nested + | Some typ -> + typ + |> extractType ~env ~package:full.package + |> Utils.Option.flatMap (fun typ -> + typ |> resolveNested ~env ~full ~nested))) + | NArray, Tarray (env, typ) -> typ |> resolveNested ~env ~full ~nested | _ -> None) let getArgs ~env (t : Types.type_expr) ~full = @@ -344,3 +378,51 @@ let typeIsUnit (typ : Types.type_expr) = when Ident.name id = "unit" -> true | _ -> false + +let rec contextPathFromCoreType (coreType : Parsetree.core_type) = + match coreType.ptyp_desc with + | Ptyp_constr ({txt = Lident "option"}, [innerTyp]) -> + innerTyp |> contextPathFromCoreType + |> Option.map (fun innerTyp -> Completable.CPOption innerTyp) + | Ptyp_constr ({txt = Lident "array"}, [innerTyp]) -> + Some (Completable.CPArray (innerTyp |> contextPathFromCoreType)) + | Ptyp_constr (lid, _) -> + Some (CPId (lid.txt |> Utils.flattenLongIdent, Type)) + | _ -> None + +let printRecordFromFields ?name (fields : field list) = + (match name with + | None -> "" + | Some name -> "type " ^ name ^ " = ") + ^ "{" + ^ (fields + |> List.map (fun f -> f.fname.txt ^ ": " ^ Shared.typeToString f.typ) + |> String.concat ", ") + ^ "}" + +let rec extractedTypeToString ?(inner = false) = function + | Tuple (_, _, typ) + | Tpolyvariant {typeExpr = typ} + | Tfunction {typ} + | Trecord {definition = `TypeExpr typ} -> + if inner then + match pathFromTypeExpr typ with + | None -> "record" (* Won't happen *) + | Some p -> p |> SharedTypes.pathIdentToString + else Shared.typeToString typ + | Tbool _ -> "bool" + | Tstring _ -> "string" + | Tarray (_, innerTyp) -> + "array<" ^ extractedTypeToString ~inner:true innerTyp ^ ">" + | Toption (_, innerTyp) -> + "option<" ^ extractedTypeToString ~inner:true innerTyp ^ ">" + | Tvariant {variantDecl; variantName} -> + if inner then variantName else Shared.declToString variantName variantDecl + | Trecord {definition = `NameOnly name; fields} -> + if inner then name else printRecordFromFields ~name fields + | TinlineRecord {fields} -> printRecordFromFields fields + +let unwrapCompletionTypeIfOption (t : SharedTypes.completionType) = + match t with + | Toption (_, unwrapped) -> unwrapped + | _ -> t diff --git a/analysis/src/Utils.ml b/analysis/src/Utils.ml index 00468a339..c7f66b66c 100644 --- a/analysis/src/Utils.ml +++ b/analysis/src/Utils.ml @@ -168,6 +168,7 @@ let rec unwrapIfOption (t : Types.type_expr) = | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> unwrapIfOption t1 | Tconstr (Path.Pident {name = "option"}, [unwrappedType], _) -> unwrappedType | _ -> t + let isReactComponent (vb : Parsetree.value_binding) = vb.pvb_attributes |> List.exists (function @@ -212,3 +213,10 @@ let rec expandPath (path : Path.t) = | Pident id -> [Ident.name id] | Pdot (p, s, _) -> s :: expandPath p | Papply _ -> [] + +module Option = struct + let flatMap f o = + match o with + | None -> None + | Some v -> f v +end diff --git a/analysis/tests/src/CompletionTypeAnnotation.res b/analysis/tests/src/CompletionTypeAnnotation.res new file mode 100644 index 000000000..bfe480ea6 --- /dev/null +++ b/analysis/tests/src/CompletionTypeAnnotation.res @@ -0,0 +1,57 @@ +type someRecord = { + age: int, + name: string, +} + +type someVariant = One | Two(bool) + +type somePolyVariant = [#one | #two(bool)] + +// let x: someRecord = +// ^com + +// let x: someRecord = {} +// ^com + +// let x: someVariant = +// ^com + +// let x: someVariant = O +// ^com + +// let x: somePolyVariant = +// ^com + +// let x: somePolyVariant = #o +// ^com + +type someFunc = (int, string) => bool + +// let x: someFunc = +// ^com + +type someTuple = (bool, option) + +// let x: someTuple = +// ^com + +// let x: someTuple = (true, ) +// ^com + +// let x: option = +// ^com + +// let x: option = Some() +// ^com + +// let x: array = +// ^com + +// let x: array = [] +// ^com + +// let x: array> = +// ^com + +// let x: option> = Some([]) +// ^com diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index 7907834ab..b36ab0940 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -379,7 +379,7 @@ Found type for function (~age: int, ~name: string) => string Complete src/Completion.res 26:13 posCursor:[26:13] posNoWhite:[26:12] Found expr:[26:3->26:13] -Completable: Cpath array->m +Completable: Cpath array->m [{ "label": "Js.Array2.mapi", "kind": 12, diff --git a/analysis/tests/src/expected/CompletionExpressions.res.txt b/analysis/tests/src/expected/CompletionExpressions.res.txt index e0a1cec63..ae108ccd3 100644 --- a/analysis/tests/src/expected/CompletionExpressions.res.txt +++ b/analysis/tests/src/expected/CompletionExpressions.res.txt @@ -497,13 +497,13 @@ Completable: Cexpression CArgument Value[fnTakingRecordWithOptVariant]($0)->reco "label": "None", "kind": 4, "tags": [], - "detail": "someVariant", + "detail": "type someVariant = One | Two | Three(int, string)", "documentation": null }, { "label": "Some(_)", "kind": 4, "tags": [], - "detail": "someVariant", + "detail": "type someVariant = One | Two | Three(int, string)", "documentation": null, "insertText": "Some(${1:_})", "insertTextFormat": 2 diff --git a/analysis/tests/src/expected/CompletionFunctionArguments.res.txt b/analysis/tests/src/expected/CompletionFunctionArguments.res.txt index 2f25a539a..a2b6236f6 100644 --- a/analysis/tests/src/expected/CompletionFunctionArguments.res.txt +++ b/analysis/tests/src/expected/CompletionFunctionArguments.res.txt @@ -158,7 +158,7 @@ Completable: Cexpression CArgument Value[someFnTakingVariant]($0)=So "label": "Some(_)", "kind": 4, "tags": [], - "detail": "someVariant", + "detail": "type someVariant = One | Two | Three(int, string)", "documentation": null, "sortText": "A Some(_)", "insertText": "Some(${1:_})", diff --git a/analysis/tests/src/expected/CompletionPattern.res.txt b/analysis/tests/src/expected/CompletionPattern.res.txt index ff00ec0a6..3a603eb50 100644 --- a/analysis/tests/src/expected/CompletionPattern.res.txt +++ b/analysis/tests/src/expected/CompletionPattern.res.txt @@ -76,7 +76,7 @@ XXX Not found! Completable: Cpattern Value[f] [{ "label": "{}", - "kind": 12, + "kind": 22, "tags": [], "detail": "someRecord", "documentation": null, @@ -166,7 +166,7 @@ posCursor:[61:22] posNoWhite:[61:21] Found pattern:[61:16->61:25] Completable: Cpattern Value[f]->recordField(nest) [{ "label": "{}", - "kind": 12, + "kind": 22, "tags": [], "detail": "nestedRecord", "documentation": null, @@ -410,7 +410,7 @@ XXX Not found! Completable: Cpattern Value[c] [{ "label": "[]", - "kind": 12, + "kind": 22, "tags": [], "detail": "bool", "documentation": null, @@ -539,7 +539,7 @@ posCursor:[143:35] posNoWhite:[143:34] Found pattern:[143:20->143:38] Completable: Cpattern Value[p]->variantPayload::Test($3) [{ "label": "[]", - "kind": 12, + "kind": 22, "tags": [], "detail": "bool", "documentation": null, @@ -625,7 +625,7 @@ posCursor:[159:36] posNoWhite:[159:35] Found pattern:[159:21->159:38] Completable: Cpattern Value[v]->polyvariantPayload::test($3) [{ "label": "[]", - "kind": 12, + "kind": 22, "tags": [], "detail": "bool", "documentation": null, diff --git a/analysis/tests/src/expected/CompletionTypeAnnotation.res.txt b/analysis/tests/src/expected/CompletionTypeAnnotation.res.txt new file mode 100644 index 000000000..6d7db71a7 --- /dev/null +++ b/analysis/tests/src/expected/CompletionTypeAnnotation.res.txt @@ -0,0 +1,296 @@ +Complete src/CompletionTypeAnnotation.res 9:22 +XXX Not found! +Completable: Cexpression Type[someRecord] +[{ + "label": "{}", + "kind": 12, + "tags": [], + "detail": "type someRecord = {age: int, name: string}", + "documentation": null, + "sortText": "A", + "insertText": "{$0}", + "insertTextFormat": 2 + }] + +Complete src/CompletionTypeAnnotation.res 12:24 +XXX Not found! +Completable: Cexpression Type[someRecord]->recordBody +[{ + "label": "age", + "kind": 5, + "tags": [], + "detail": "age: int\n\ntype someRecord = {age: int, name: string}", + "documentation": null + }, { + "label": "name", + "kind": 5, + "tags": [], + "detail": "name: string\n\ntype someRecord = {age: int, name: string}", + "documentation": null + }] + +Complete src/CompletionTypeAnnotation.res 15:23 +XXX Not found! +Completable: Cexpression Type[someVariant] +[{ + "label": "One", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two(bool)", + "documentation": null, + "insertText": "One", + "insertTextFormat": 2 + }, { + "label": "Two(_)", + "kind": 4, + "tags": [], + "detail": "Two(bool)\n\ntype someVariant = One | Two(bool)", + "documentation": null, + "insertText": "Two(${1:_})", + "insertTextFormat": 2 + }] + +Complete src/CompletionTypeAnnotation.res 18:25 +XXX Not found! +Completable: Cexpression Type[someVariant]=O +[{ + "label": "One", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two(bool)", + "documentation": null, + "sortText": "A One", + "insertText": "One", + "insertTextFormat": 2 + }, { + "label": "Obj", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null + }, { + "label": "Object", + "kind": 9, + "tags": [], + "detail": "file module", + "documentation": null + }] + +Complete src/CompletionTypeAnnotation.res 21:27 +XXX Not found! +Completable: Cexpression Type[somePolyVariant] +[{ + "label": "#one", + "kind": 4, + "tags": [], + "detail": "#one\n\n[#one | #two(bool)]", + "documentation": null, + "insertText": "#one", + "insertTextFormat": 2 + }, { + "label": "#two(_)", + "kind": 4, + "tags": [], + "detail": "#two(bool)\n\n[#one | #two(bool)]", + "documentation": null, + "insertText": "#two(${1:_})", + "insertTextFormat": 2 + }] + +Complete src/CompletionTypeAnnotation.res 24:30 +XXX Not found! +Completable: Cexpression Type[somePolyVariant]=#o +[{ + "label": "#one", + "kind": 4, + "tags": [], + "detail": "#one\n\n[#one | #two(bool)]", + "documentation": null, + "insertText": "one", + "insertTextFormat": 2 + }] + +Complete src/CompletionTypeAnnotation.res 29:20 +XXX Not found! +Completable: Cexpression Type[someFunc] +[{ + "label": "(v1, v2) => {}", + "kind": 12, + "tags": [], + "detail": "(int, string) => bool", + "documentation": null, + "sortText": "A", + "insertText": "(${1:v1}, ${2:v2}) => {$0}", + "insertTextFormat": 2 + }] + +Complete src/CompletionTypeAnnotation.res 34:21 +XXX Not found! +Completable: Cexpression Type[someTuple] +[{ + "label": "(_, _)", + "kind": 12, + "tags": [], + "detail": "(bool, option)", + "documentation": null, + "insertText": "(${1:_}, ${2:_})", + "insertTextFormat": 2 + }] + +Complete src/CompletionTypeAnnotation.res 37:28 +XXX Not found! +Completable: Cexpression Type[someTuple]->tuple($1) +[{ + "label": "None", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(_)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null, + "insertText": "Some(${1:_})", + "insertTextFormat": 2 + }, { + "label": "Some(true)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(false)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionTypeAnnotation.res 40:31 +XXX Not found! +Completable: Cexpression option +[{ + "label": "None", + "kind": 4, + "tags": [], + "detail": "type someVariant = One | Two(bool)", + "documentation": null + }, { + "label": "Some(_)", + "kind": 4, + "tags": [], + "detail": "type someVariant = One | Two(bool)", + "documentation": null, + "insertText": "Some(${1:_})", + "insertTextFormat": 2 + }, { + "label": "Some(One)", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two(bool)", + "documentation": null, + "insertText": "Some(One)", + "insertTextFormat": 2 + }, { + "label": "Some(Two(_))", + "kind": 4, + "tags": [], + "detail": "Two(bool)\n\ntype someVariant = One | Two(bool)", + "documentation": null, + "insertText": "Some(Two(${1:_}))", + "insertTextFormat": 2 + }] + +Complete src/CompletionTypeAnnotation.res 43:37 +XXX Not found! +Completable: Cexpression option->variantPayload::Some($0) +[{ + "label": "One", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two(bool)", + "documentation": null, + "insertText": "One", + "insertTextFormat": 2 + }, { + "label": "Two(_)", + "kind": 4, + "tags": [], + "detail": "Two(bool)\n\ntype someVariant = One | Two(bool)", + "documentation": null, + "insertText": "Two(${1:_})", + "insertTextFormat": 2 + }] + +Complete src/CompletionTypeAnnotation.res 46:30 +XXX Not found! +Completable: Cexpression array +[{ + "label": "[]", + "kind": 12, + "tags": [], + "detail": "type someVariant = One | Two(bool)", + "documentation": null, + "sortText": "A", + "insertText": "[$0]", + "insertTextFormat": 2 + }] + +Complete src/CompletionTypeAnnotation.res 49:32 +XXX Not found! +Completable: Cexpression array->array +[{ + "label": "One", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two(bool)", + "documentation": null, + "insertText": "One", + "insertTextFormat": 2 + }, { + "label": "Two(_)", + "kind": 4, + "tags": [], + "detail": "Two(bool)\n\ntype someVariant = One | Two(bool)", + "documentation": null, + "insertText": "Two(${1:_})", + "insertTextFormat": 2 + }] + +Complete src/CompletionTypeAnnotation.res 52:38 +XXX Not found! +Completable: Cexpression array> +[{ + "label": "[]", + "kind": 12, + "tags": [], + "detail": "option", + "documentation": null, + "sortText": "A", + "insertText": "[$0]", + "insertTextFormat": 2 + }] + +Complete src/CompletionTypeAnnotation.res 55:45 +XXX Not found! +Completable: Cexpression option>->variantPayload::Some($0), array +[{ + "label": "One", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two(bool)", + "documentation": null, + "insertText": "One", + "insertTextFormat": 2 + }, { + "label": "Two(_)", + "kind": 4, + "tags": [], + "detail": "Two(bool)\n\ntype someVariant = One | Two(bool)", + "documentation": null, + "insertText": "Two(${1:_})", + "insertTextFormat": 2 + }] +