diff --git a/CHANGELOG.md b/CHANGELOG.md index 46181e0ef..23e37dadd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Add autocomplete for JSX prop values. https://github.com/rescript-lang/rescript-vscode/pull/667 - Add snippet support in completion items. https://github.com/rescript-lang/rescript-vscode/pull/668 - Add support from completing polyvariants as values. https://github.com/rescript-lang/rescript-vscode/pull/669 +- Add support for completion in patterns. https://github.com/rescript-lang/rescript-vscode/pull/670 #### :nail_care: Polish diff --git a/analysis/src/CompletionBackEnd.ml b/analysis/src/CompletionBackEnd.ml index 3568d3581..0333cf350 100644 --- a/analysis/src/CompletionBackEnd.ml +++ b/analysis/src/CompletionBackEnd.ml @@ -1519,6 +1519,8 @@ let rec extractType ~env ~package (t : Types.type_expr) = | Tlink t1 | Tsubst t1 | Tpoly (t1, []) -> extractType ~env ~package t1 | Tconstr (Path.Pident {name = "option"}, [payloadTypeExpr], _) -> Some (Completable.Toption (env, payloadTypeExpr)) + | Tconstr (Path.Pident {name = "array"}, [payloadTypeExpr], _) -> + Some (Tarray (env, payloadTypeExpr)) | Tconstr (Path.Pident {name = "bool"}, [], _) -> Some (Tbool env) | Tconstr (path, _, _) -> ( match References.digConstructor ~env ~package path with @@ -1528,6 +1530,8 @@ let rec extractType ~env ~package (t : Types.type_expr) = Some (Tvariant {env; constructors; variantName = name.txt; variantDecl = decl}) + | Some (env, {item = {kind = Record fields}}) -> + Some (Trecord {env; fields; typeExpr = t}) | _ -> None) | Ttuple expressions -> Some (Tuple (env, expressions, t)) | Tvariant {row_fields} -> @@ -1566,7 +1570,7 @@ let printConstructorArgs argsLen ~asSnippet = else "" let completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix - ~expandOption = + ~expandOption ~includeLocalValues ~completionContext = let namesUsed = Hashtbl.create 10 in let rec completeTypedValueInner t ~env ~full ~prefix ~expandOption = let items = @@ -1641,10 +1645,36 @@ let completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix ~insertText:(printConstructorArgs numExprs ~asSnippet:true) ~kind:(Value typ) ~env (); ] + | Some (Trecord {env; fields; typeExpr}) -> ( + (* 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. *) + match completionContext with + | Some (Completable.RecordField {seenFields}) -> + fields + |> List.filter (fun (field : field) -> + List.mem field.fname.txt seenFields = false) + |> List.map (fun (field : field) -> + Completion.create ~name:field.fname.txt + ~kind:(Field (field, typeExpr |> Shared.typeToString)) + ~env) + |> filterItems ~prefix + | None -> + [ + Completion.createWithSnippet ~name:"{}" + ~insertText:(if !Cfg.supportsSnippets then "{$0}" else "{}") + ~sortText:"a" ~kind:(Value typeExpr) ~env (); + ]) + | Some (Tarray (env, typeExpr)) -> + [ + Completion.createWithSnippet ~name:"[]" + ~insertText:(if !Cfg.supportsSnippets then "[$0]" else "[]") + ~sortText:"a" ~kind:(Value typeExpr) ~env (); + ] | _ -> [] in (* Include all values and modules in completion if there's a prefix, not otherwise *) - if prefix = "" then items + if prefix = "" || includeLocalValues = false then items else items @ completionForExportedValues ~env:envWhereCompletionStarted ~prefix @@ -1729,7 +1759,57 @@ let getJsxLabels ~componentPath ~findTypeOfValue ~package = typ |> getLabels | None -> [] -let processCompletable ~debug ~full ~scope ~env ~pos ~forHover +(** This moves through a pattern via a set of instructions, trying to resolve the type at the end of the pattern. *) +let rec resolveNestedPattern typ ~env ~package ~nested = + match nested with + | [] -> Some (typ, env, None) + | patternPath :: nested -> ( + match (patternPath, typ |> extractType ~env ~package) with + | Completable.PTupleItem {itemNum}, Some (Tuple (env, tupleItems, _)) -> ( + match List.nth_opt tupleItems itemNum with + | None -> None + | Some typ -> typ |> resolveNestedPattern ~env ~package ~nested) + | PFollowRecordField {fieldName}, Some (Trecord {env; fields}) -> ( + match + fields + |> List.find_opt (fun (field : field) -> field.fname.txt = fieldName) + with + | None -> None + | Some {typ} -> typ |> resolveNestedPattern ~env ~package ~nested) + | PRecordBody {seenFields}, Some (Trecord {env; typeExpr}) -> + Some (typeExpr, env, Some (Completable.RecordField {seenFields})) + | ( PVariantPayload {constructorName = "Some"; itemNum = 0}, + Some (Toption (env, typ)) ) -> + typ |> resolveNestedPattern ~env ~package ~nested + | ( PVariantPayload {constructorName; itemNum}, + Some (Tvariant {env; constructors}) ) -> ( + match + constructors + |> List.find_opt (fun (c : Constructor.t) -> + c.cname.txt = constructorName) + with + | None -> None + | Some constructor -> ( + match List.nth_opt constructor.args itemNum with + | None -> None + | Some (typ, _) -> typ |> resolveNestedPattern ~env ~package ~nested)) + | ( PPolyvariantPayload {constructorName; itemNum}, + Some (Tpolyvariant {env; constructors}) ) -> ( + match + constructors + |> List.find_opt (fun (c : polyVariantConstructor) -> + c.name = constructorName) + with + | None -> None + | Some constructor -> ( + match List.nth_opt constructor.args itemNum with + | None -> None + | Some typ -> typ |> resolveNestedPattern ~env ~package ~nested)) + | PArray, Some (Tarray (env, typ)) -> + typ |> resolveNestedPattern ~env ~package ~nested + | _ -> None) + +let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover (completable : Completable.t) = let package = full.package in let rawOpens = Scope.getRawOpens scope in @@ -1792,7 +1872,7 @@ let processCompletable ~debug ~full ~scope ~env ~pos ~forHover | Some (_, typ, env) -> typ |> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix - ~expandOption:true) + ~expandOption:true ~includeLocalValues:true ~completionContext:None) | Cdecorator prefix -> let mkDecorator (name, docstring) = {(Completion.create ~name ~kind:(Label "") ~env) with docstring} @@ -2057,11 +2137,11 @@ Note: The `@react.component` decorator requires the react-jsx config to be set i | Some (Optional _, typ) -> typ |> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix - ~expandOption:true + ~expandOption:true ~includeLocalValues:true ~completionContext:None | Some ((Unlabelled _ | Labelled _), typ) -> typ |> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix - ~expandOption:false) + ~expandOption:false ~includeLocalValues:true ~completionContext:None) | CnamedArg (cp, prefix, identsSeen) -> let labels = match @@ -2091,3 +2171,29 @@ Note: The `@react.component` decorator requires the react-jsx config to be set i Utils.startsWith name prefix && (forHover || not (List.mem name identsSeen))) |> List.map mkLabel + | Cpattern {typ; prefix; nested; fallback} -> ( + let fallbackOrEmpty ?items () = + match (fallback, items) with + | Some fallback, (None | Some []) -> + fallback |> processCompletable ~debug ~full ~scope ~env ~pos ~forHover + | _, Some items -> items + | None, None -> [] + in + let envWhereCompletionStarted = env in + match + typ + |> getCompletionsForContextPath ~full ~opens ~rawOpens ~allFiles ~pos ~env + ~exact:true ~scope + |> completionsGetTypeEnv + with + | Some (typ, env) -> ( + match typ |> resolveNestedPattern ~env ~package:full.package ~nested with + | None -> fallbackOrEmpty () + | Some (typ, env, completionContext) -> + let items = + typ + |> completeTypedValue ~env ~envWhereCompletionStarted ~full ~prefix + ~expandOption:false ~includeLocalValues:false ~completionContext + in + fallbackOrEmpty ~items ()) + | None -> fallbackOrEmpty ()) diff --git a/analysis/src/CompletionFrontEnd.ml b/analysis/src/CompletionFrontEnd.ml index aaeee4dd1..007625c53 100644 --- a/analysis/src/CompletionFrontEnd.ml +++ b/analysis/src/CompletionFrontEnd.ml @@ -20,6 +20,16 @@ let isExprHole exp = | Pexp_extension ({txt = "rescript.exprhole"}, _) -> true | _ -> false +let isPatternHole pat = + match pat.Parsetree.ppat_desc with + | Ppat_extension ({txt = "rescript.patternhole"}, _) -> true + | _ -> false + +let isPatternTuple pat = + match pat.Parsetree.ppat_desc with + | Ppat_tuple _ -> true + | _ -> false + type prop = { name: string; posStart: int * int; @@ -259,6 +269,12 @@ let rec exprToContextPath (e : Parsetree.expression) = | Some contexPath -> Some (CPApply (contexPath, args |> List.map fst))) | _ -> None +let rec getUnqualifiedName txt = + match txt with + | Longident.Lident fieldName -> fieldName + | Ldot (t, _) -> getUnqualifiedName t + | _ -> "" + let completePipeChain ~(lhs : Parsetree.expression) = (* Complete the end of pipe chains by reconstructing the pipe chain as a single pipe, so it can be completed. @@ -307,6 +323,15 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = let line, col = posCursor in (line, max 0 col - offset + offsetNoWhite) in + (* Identifies the first character before the cursor that's not white space. + Should be used very sparingly, but can be used to drive completion triggering + in scenarios where the parser eats things we'd need to complete. + Example: let {whatever, }, char is ','. *) + let firstCharBeforeCursorNoWhite = + if offsetNoWhite < String.length text && offsetNoWhite >= 0 then + Some text.[offsetNoWhite] + else None + in let posBeforeCursor = Pos.posBeforeCursor posCursor in let charBeforeCursor, blankAfterCursor = match Pos.positionToOffset text posCursor with @@ -377,10 +402,240 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = | Ppat_open (_, p) -> scopePattern p in + let lookingForPat = ref None in + + let locHasCursor = CursorPosition.locHasCursor ~pos:posBeforeCursor in + let locIsEmpty = CursorPosition.locIsEmpty ~pos:posBeforeCursor in + + let rec traverseTupleItems tupleItems ~nextPatternPath ~resultFromFoundItemNum + = + let itemNum = ref (-1) in + let itemWithCursor = + tupleItems + |> List.find_map (fun pat -> + itemNum := !itemNum + 1; + pat |> traversePattern ~patternPath:(nextPatternPath !itemNum)) + in + match (itemWithCursor, firstCharBeforeCursorNoWhite) with + | None, Some ',' -> + (* No tuple item has the cursor, but there's a comma before the cursor. + Figure out what arg we're trying to complete. Example: (true, , None) *) + let posNum = ref (-1) in + tupleItems + |> List.iteri (fun index pat -> + if posBeforeCursor >= Loc.start pat.Parsetree.ppat_loc then + posNum := index); + if !posNum > -1 then Some ("", resultFromFoundItemNum !posNum) else None + | v, _ -> v + and traversePattern (pat : Parsetree.pattern) ~patternPath = + let someIfHasCursor v = + if locHasCursor pat.Parsetree.ppat_loc then Some v else None + in + match pat.ppat_desc with + | Ppat_any | Ppat_constant _ | Ppat_interval _ -> None + | Ppat_lazy p + | Ppat_constraint (p, _) + | Ppat_alias (p, _) + | Ppat_exception p + | Ppat_open (_, p) -> + p |> traversePattern ~patternPath + | Ppat_or (p1, p2) -> ( + let orPatWithItem = + [p1; p2] |> List.find_map (fun p -> p |> traversePattern ~patternPath) + in + match orPatWithItem with + | None when isPatternHole p1 || isPatternHole p2 -> Some ("", patternPath) + | v -> v) + | Ppat_var {txt} -> someIfHasCursor (txt, patternPath) + | Ppat_construct ({txt = Lident "()"}, None) -> + (* switch s { | () }*) + someIfHasCursor ("", patternPath @ [Completable.PTupleItem {itemNum = 0}]) + | Ppat_construct ({txt = Lident prefix}, None) -> + someIfHasCursor (prefix, patternPath) + | Ppat_variant (prefix, None) -> someIfHasCursor ("#" ^ prefix, patternPath) + | Ppat_array arrayPatterns -> + let nextPatternPath = [Completable.PArray] @ patternPath in + if List.length arrayPatterns = 0 && locHasCursor pat.ppat_loc then + Some ("", nextPatternPath) + else + arrayPatterns + |> List.find_map (fun pat -> + pat |> traversePattern ~patternPath:nextPatternPath) + | Ppat_tuple tupleItems when locHasCursor pat.ppat_loc -> + tupleItems + |> traverseTupleItems + ~nextPatternPath:(fun itemNum -> + [Completable.PTupleItem {itemNum}] @ patternPath) + ~resultFromFoundItemNum:(fun itemNum -> + [Completable.PTupleItem {itemNum = itemNum + 1}] @ patternPath) + | Ppat_record ([], _) -> + (* Empty fields means we're in a record body `{}`. Complete for the fields. *) + someIfHasCursor + ("", [Completable.PRecordBody {seenFields = []}] @ patternPath) + | Ppat_record (fields, _) -> ( + let fieldWithCursor = ref None in + let fieldWithPatHole = ref None in + fields + |> List.iter (fun (fname, f) -> + match + ( fname.Location.txt, + f.Parsetree.ppat_loc + |> CursorPosition.classifyLoc ~pos:posBeforeCursor ) + with + | Longident.Lident fname, HasCursor -> + fieldWithCursor := Some (fname, f) + | Lident fname, _ when isPatternHole f -> + fieldWithPatHole := Some (fname, f) + | _ -> ()); + let seenFields = + fields + |> List.filter_map (fun (fieldName, _f) -> + match fieldName with + | {Location.txt = Longident.Lident fieldName} -> Some fieldName + | _ -> None) + in + match (!fieldWithCursor, !fieldWithPatHole) with + | Some (fname, f), _ | None, Some (fname, f) -> ( + match f.ppat_desc with + | Ppat_extension ({txt = "rescript.patternhole"}, _) -> + (* A pattern hole means for example `{someField: }`. We want to complete for the type of `someField`. *) + someIfHasCursor + ( "", + [Completable.PFollowRecordField {fieldName = fname}] @ patternPath + ) + | Ppat_var {txt} -> + (* A var means `{s}` or similar. Complete for fields. *) + someIfHasCursor + (txt, [Completable.PRecordBody {seenFields}] @ patternPath) + | _ -> + f + |> traversePattern + ~patternPath: + ([Completable.PFollowRecordField {fieldName = fname}] + @ patternPath)) + | None, None -> ( + (* Figure out if we're completing for a new field. + If the cursor is inside of the record body, but no field has the cursor, + and there's no pattern hole. Check the first char to the left of the cursor, + ignoring white space. If that's a comma, we assume you're completing for a new field. *) + match firstCharBeforeCursorNoWhite with + | Some ',' -> + someIfHasCursor + ("", [Completable.PRecordBody {seenFields}] @ patternPath) + | _ -> None)) + | Ppat_construct + ( {txt}, + Some {ppat_loc; ppat_desc = Ppat_construct ({txt = Lident "()"}, _)} + ) + when locHasCursor ppat_loc -> + (* Empty payload with cursor, like: Test() *) + Some + ( "", + [ + Completable.PVariantPayload + {constructorName = getUnqualifiedName txt; itemNum = 0}; + ] + @ patternPath ) + | Ppat_construct ({txt}, Some pat) + when posBeforeCursor >= (pat.ppat_loc |> Loc.end_) + && firstCharBeforeCursorNoWhite = Some ',' + && isPatternTuple pat = false -> + (* Empty payload with trailing ',', like: Test(true, ) *) + Some + ( "", + [ + Completable.PVariantPayload + {constructorName = getUnqualifiedName txt; itemNum = 1}; + ] + @ patternPath ) + | Ppat_construct ({txt}, Some {ppat_loc; ppat_desc = Ppat_tuple tupleItems}) + when locHasCursor ppat_loc -> + tupleItems + |> traverseTupleItems + ~nextPatternPath:(fun itemNum -> + [ + Completable.PVariantPayload + {constructorName = getUnqualifiedName txt; itemNum}; + ] + @ patternPath) + ~resultFromFoundItemNum:(fun itemNum -> + [ + Completable.PVariantPayload + { + constructorName = getUnqualifiedName txt; + itemNum = itemNum + 1; + }; + ] + @ patternPath) + | Ppat_construct ({txt}, Some p) when locHasCursor pat.ppat_loc -> + p + |> traversePattern + ~patternPath: + ([ + Completable.PVariantPayload + {constructorName = getUnqualifiedName txt; itemNum = 0}; + ] + @ patternPath) + | Ppat_variant + ( txt, + Some {ppat_loc; ppat_desc = Ppat_construct ({txt = Lident "()"}, _)} + ) + when locHasCursor ppat_loc -> + (* Empty payload with cursor, like: #test() *) + Some + ( "", + [Completable.PPolyvariantPayload {constructorName = txt; itemNum = 0}] + @ patternPath ) + | Ppat_variant (txt, Some pat) + when posBeforeCursor >= (pat.ppat_loc |> Loc.end_) + && firstCharBeforeCursorNoWhite = Some ',' + && isPatternTuple pat = false -> + (* Empty payload with trailing ',', like: #test(true, ) *) + Some + ( "", + [Completable.PPolyvariantPayload {constructorName = txt; itemNum = 1}] + @ patternPath ) + | Ppat_variant (txt, Some {ppat_loc; ppat_desc = Ppat_tuple tupleItems}) + when locHasCursor ppat_loc -> + tupleItems + |> traverseTupleItems + ~nextPatternPath:(fun itemNum -> + [Completable.PPolyvariantPayload {constructorName = txt; itemNum}] + @ patternPath) + ~resultFromFoundItemNum:(fun itemNum -> + [ + Completable.PPolyvariantPayload + {constructorName = txt; itemNum = itemNum + 1}; + ] + @ patternPath) + | Ppat_variant (txt, Some p) when locHasCursor pat.ppat_loc -> + p + |> traversePattern + ~patternPath: + ([ + Completable.PPolyvariantPayload + {constructorName = txt; itemNum = 0}; + ] + @ patternPath) + | _ -> None + in + let completePattern (pat : Parsetree.pattern) = + match (pat |> traversePattern ~patternPath:[], !lookingForPat) with + | Some (prefix, nestedPattern), Some ctxPath -> + setResult + (Completable.Cpattern + { + typ = ctxPath; + prefix; + nested = List.rev nestedPattern; + fallback = None; + }) + | _ -> () + in let scopeValueBinding (vb : Parsetree.value_binding) = - scopePattern vb.pvb_pat + scopePattern vb.pvb_pat; + completePattern vb.pvb_pat in - let scopeTypeKind (tk : Parsetree.type_kind) = match tk with | Ptype_variant constrDecls -> @@ -409,10 +664,72 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = scope := !scope |> Scope.addModule ~name:md.pmd_name.txt ~loc:md.pmd_name.loc in + let setLookingForPat ctxPath = lookingForPat := Some ctxPath in + + let unsetLookingForPat () = lookingForPat := None in + (* Identifies expressions where we can do typed pattern or expr completion. *) + let typedCompletionExpr (exp : Parsetree.expression) = + if + exp.pexp_loc + |> CursorPosition.classifyLoc ~pos:posBeforeCursor + = HasCursor + then + match exp.pexp_desc with + | Pexp_match (_exp, []) -> + (* No cases means there's no `|` yet in the switch *) () + | Pexp_match + ( exp, + [ + { + pc_lhs = + { + ppat_desc = + Ppat_extension ({txt = "rescript.patternhole"}, _); + }; + }; + ] ) -> ( + (* A single case that's a pattern hole typically means `switch x { | }`. Complete as the pattern itself with nothing nested. *) + match exprToContextPath exp with + | None -> () + | Some ctxPath -> + setResult + (Completable.Cpattern + {typ = ctxPath; nested = []; prefix = ""; fallback = None})) + | Pexp_match (exp, cases) -> ( + (* If there's more than one case, or the case isn't a pattern hole, figure out if we're completing another + broken parser case (`switch x { | true => () | }` for example). *) + match exp |> exprToContextPath with + | None -> () + | Some ctxPath -> ( + let hasCaseWithCursor = + cases + |> List.find_opt (fun case -> + locHasCursor case.Parsetree.pc_lhs.ppat_loc) + |> Option.is_some + in + let hasCaseWithEmptyLoc = + cases + |> List.find_opt (fun case -> + locIsEmpty case.Parsetree.pc_lhs.ppat_loc) + |> Option.is_some + in + match (hasCaseWithEmptyLoc, hasCaseWithCursor) with + | _, true -> + (* Always continue if there's a case with the cursor *) + setLookingForPat ctxPath + | true, false -> + (* If there's no case with the cursor, but a broken parser case, complete for the top level. *) + setResult + (Completable.Cpattern + {typ = ctxPath; nested = []; prefix = ""; fallback = None}) + | false, false -> ())) + | _ -> unsetLookingForPat () + in let case (iterator : Ast_iterator.iterator) (case : Parsetree.case) = let oldScope = !scope in scopePattern case.pc_lhs; + completePattern case.pc_lhs; Ast_iterator.default_iterator.case iterator case; scope := oldScope in @@ -424,12 +741,20 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = in let structure_item (iterator : Ast_iterator.iterator) (item : Parsetree.structure_item) = + unsetLookingForPat (); let processed = ref false in (match item.pstr_desc with | Pstr_open {popen_lid} -> scope := !scope |> Scope.addOpen ~lid:popen_lid.txt | Pstr_primitive vd -> scopeValueDescription vd | Pstr_value (recFlag, bindings) -> + (* Identify relevant destructures for completion, like `let {} = someVar` or `let (true, false) = someFn()`. *) + (match bindings with + | [{pvb_pat = {ppat_desc = Ppat_record _ | Ppat_tuple _}; pvb_expr}] -> ( + match exprToContextPath pvb_expr with + | None -> () + | Some ctxPath -> setLookingForPat ctxPath) + | _ -> ()); if recFlag = Recursive then bindings |> List.iter scopeValueBinding; bindings |> List.iter (fun vb -> iterator.value_binding iterator vb); if recFlag = Nonrecursive then bindings |> List.iter scopeValueBinding; @@ -548,6 +873,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = setResult (Cpath (CPPipe {contextPath = pipe; id; lhsLoc})); true in + typedCompletionExpr expr; match expr.pexp_desc with | Pexp_apply ( {pexp_desc = Pexp_ident {txt = Lident "|."; loc = opLoc}}, @@ -742,6 +1068,7 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = | None -> () | Some defaultExp -> iterator.expr iterator defaultExp); scopePattern pat; + completePattern pat; iterator.pat iterator pat; iterator.expr iterator e; scope := oldScope; @@ -799,13 +1126,17 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text = (Pos.toString posCursor) (Pos.toString posNoWhite) (Loc.toString pat.ppat_loc); (match pat.ppat_desc with - | Ppat_construct (lid, _) -> + | Ppat_construct (lid, _) -> ( let lidPath = flattenLidCheckDot lid in if debug then Printf.printf "Ppat_construct %s:%s\n" (lidPath |> String.concat ".") (Loc.toString lid.loc); - setResult (Cpath (CPId (lidPath, Value))) + let completion = Completable.Cpath (CPId (lidPath, Value)) in + match !result with + | Some (Completable.Cpattern p, scope) -> + result := Some (Cpattern {p with fallback = Some completion}, scope) + | _ -> setResult completion) | _ -> ()); Ast_iterator.default_iterator.pat iterator pat) in diff --git a/analysis/src/SharedTypes.ml b/analysis/src/SharedTypes.ml index ef2808427..1da08ec8c 100644 --- a/analysis/src/SharedTypes.ml +++ b/analysis/src/SharedTypes.ml @@ -550,6 +550,29 @@ module Completable = struct (** The loc item for the left hand side of the pipe. *) } + (** Additional context for a pattern completion where needed. *) + type patternContext = RecordField of {seenFields: string list} + + type patternPath = + | PTupleItem of {itemNum: int} + | PFollowRecordField of {fieldName: string} + | PRecordBody of {seenFields: string list} + | PVariantPayload of {constructorName: string; itemNum: int} + | PPolyvariantPayload of {constructorName: string; itemNum: int} + | PArray + + let patternPathToString p = + match p with + | PTupleItem {itemNum} -> "tuple($" ^ string_of_int itemNum ^ ")" + | PFollowRecordField {fieldName} -> "recordField(" ^ fieldName ^ ")" + | PRecordBody _ -> "recordBody" + | PVariantPayload {constructorName; itemNum} -> + "variantPayload::" ^ constructorName ^ "($" ^ string_of_int itemNum ^ ")" + | PPolyvariantPayload {constructorName; itemNum} -> + "polyvariantPayload::" ^ constructorName ^ "($" ^ string_of_int itemNum + ^ ")" + | PArray -> "array" + type t = | Cdecorator of string (** e.g. @module *) | CnamedArg of contextPath * string * string list @@ -569,12 +592,19 @@ module Completable = struct propName: string; prefix: string; } + | Cpattern of { + typ: contextPath; + nested: patternPath list; + prefix: string; + fallback: t option; + } (** 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 | Tvariant of { env: QueryEnv.t; constructors: Constructor.t list; @@ -586,6 +616,11 @@ module Completable = struct constructors: polyVariantConstructor list; typeExpr: Types.type_expr; } + | Trecord of { + env: QueryEnv.t; + fields: field list; + typeExpr: Types.type_expr; + } let toString = let completionContextToString = function @@ -614,6 +649,7 @@ module Completable = struct | CPObj (cp, s) -> contextPathToString cp ^ "[\"" ^ s ^ "\"]" | CPPipe {contextPath; id} -> contextPathToString contextPath ^ "->" ^ id in + function | Cpath cp -> "Cpath " ^ contextPathToString cp | Cdecorator s -> "Cdecorator(" ^ str s ^ ")" @@ -637,6 +673,17 @@ module Completable = struct | CjsxPropValue {prefix; pathToComponent; propName} -> "CjsxPropValue " ^ (pathToComponent |> list) ^ " " ^ propName ^ "=" ^ prefix + | Cpattern {typ; nested; prefix} -> ( + "Cpattern " ^ contextPathToString typ + ^ (if prefix = "" then "" else "=" ^ prefix) + ^ + match nested with + | [] -> "" + | patternPaths -> + "->" + ^ (patternPaths + |> List.map (fun patternPath -> patternPathToString patternPath) + |> String.concat ", ")) end module CursorPosition = struct @@ -657,6 +704,10 @@ module CursorPosition = struct if posStart <= pos && pos <= posEnd then HasCursor else if posEnd = (Location.none |> Loc.end_) then EmptyLoc else NoCursor + + let locHasCursor loc ~pos = loc |> classifyLoc ~pos = HasCursor + + let locIsEmpty loc ~pos = loc |> classifyLoc ~pos = EmptyLoc end type labelled = { diff --git a/analysis/tests/src/BrokenParserCases.res b/analysis/tests/src/BrokenParserCases.res index 877b7b0e4..f748ed0f5 100644 --- a/analysis/tests/src/BrokenParserCases.res +++ b/analysis/tests/src/BrokenParserCases.res @@ -3,3 +3,7 @@ // let _ = someFn(~isOff=, ()) // ^com +// This should parse as a single item tuple when in a pattern? +// switch s { | (t) } +// ^com + diff --git a/analysis/tests/src/CompletionPattern.res b/analysis/tests/src/CompletionPattern.res new file mode 100644 index 000000000..26cba1167 --- /dev/null +++ b/analysis/tests/src/CompletionPattern.res @@ -0,0 +1,193 @@ +let v = (true, Some(false), (true, true)) + +let _ = switch v { +| (true, _, _) => 1 +| _ => 2 +} + +// switch v { +// ^com + +// switch v { | } +// ^com + +// switch v { | (t, _) } +// ^com + +// switch v { | (_, _, (f, _)) } +// ^com + +let x = true + +// switch x { | +// ^com + +// switch x { | t +// ^com + +type nestedRecord = {nested: bool} + +type rec someRecord = { + first: int, + second: (bool, option), + optThird: option<[#first | #second(someRecord)]>, + nest: nestedRecord, +} + +let f: someRecord = { + first: 123, + second: (true, None), + optThird: None, + nest: {nested: true}, +} + +let z = (f, true) +ignore(z) + +// switch f { | } +// ^com + +// switch f { | {}} +// ^com + +// switch f { | {first, , second }} +// ^com + +// switch f { | {fi}} +// ^com + +// switch z { | ({o}, _)} +// ^com + +// switch f { | {nest: }} +// ^com + +// switch f { | {nest: {}}} +// ^com + +let _ = switch f { +| {first: 123, nest} => + () + // switch nest { | {}} + // ^com + nest.nested +| _ => false +} + +// let {} = f +// ^com + +// let {nest: {n}}} = f +// ^com + +type someVariant = One | Two(bool) | Three(someRecord, bool) + +let z = Two(true) +ignore(z) + +// switch z { | Two()} +// ^com + +// switch z { | Two(t)} +// ^com + +// switch z { | Three({})} +// ^com + +// switch z { | Three({}, t)} +// ^com + +type somePolyVariant = [#one | #two(bool) | #three(someRecord, bool)] +let b: somePolyVariant = #two(true) +ignore(b) + +// switch b { | #two()} +// ^com + +// switch b { | #two(t)} +// ^com + +// switch b { | #three({})} +// ^com + +// switch b { | #three({}, t)} +// ^com + +let c: array = [] +ignore(c) + +// switch c { | } +// ^com + +// switch c { | [] } +// ^com + +let o = Some(true) +ignore(o) + +// switch o { | Some() } +// ^com + +type multiPayloadVariant = Test(int, bool, option, array) + +let p = Test(1, true, Some(false), []) + +// switch p { | Test(1, )} +// ^com + +// switch p { | Test(1, true, )} +// ^com + +// switch p { | Test(1, , None)} +// ^com + +// switch p { | Test(1, true, None, )} +// ^com + +type multiPayloadPolyVariant = [#test(int, bool, option, array)] + +let v: multiPayloadPolyVariant = #test(1, true, Some(false), []) + +// switch v { | #test(1, )} +// ^com + +// switch v { | #test(1, true, )} +// ^com + +// switch v { | #test(1, , None)} +// ^com + +// switch v { | #test(1, true, None, )} +// ^com + +let s = (true, Some(true), [false]) + +// switch s { | () } +// ^com + +// switch s { | (true, ) } +// ^com + +// switch s { | (true, , []) } +// ^com + +// switch s { | (true, []) => () | } +// ^com + +// switch s { | (true, []) => () | (true, , []) } +// ^com + +// switch z { | One | } +// ^com + +// switch z { | One | Two(true | ) } +// ^com + +// switch z { | One | Three({test: true}, true | ) } +// ^com + +// switch b { | #one | #two(true | ) } +// ^com + +// switch b { | #one | #three({test: true}, true | ) } +// ^com diff --git a/analysis/tests/src/expected/BrokenParserCases.res.txt b/analysis/tests/src/expected/BrokenParserCases.res.txt index 7e4f89500..d64b3ea80 100644 --- a/analysis/tests/src/expected/BrokenParserCases.res.txt +++ b/analysis/tests/src/expected/BrokenParserCases.res.txt @@ -4,3 +4,9 @@ Pexp_apply ...[2:11->2:17] (~isOff2:19->2:24=...[2:27->2:29]) Completable: Cargument Value[someFn]($0) [] +Complete src/BrokenParserCases.res 6:18 +posCursor:[6:18] posNoWhite:[6:17] Found expr:[6:3->6:21] +posCursor:[6:18] posNoWhite:[6:17] Found pattern:[6:16->6:19] +Completable: Cpattern Value[s]=t +[] + diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index 56d4cd5a6..8a4286eab 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -1446,7 +1446,9 @@ posCursor:[362:8] posNoWhite:[362:7] Found expr:[361:2->365:3] posCursor:[362:8] posNoWhite:[362:7] Found pattern:[362:7->364:5] posCursor:[362:8] posNoWhite:[362:7] Found pattern:[362:7->362:8] Ppat_construct T:[362:7->362:8] -Completable: Cpath Value[T] +Completable: Cpattern Value[x]=T +Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder +Resolved opens 2 Completion.res Completion.res Raw opens: 2 Shadow.B.place holder ... Shadow.A.place holder Resolved opens 2 Completion.res Completion.res [{ diff --git a/analysis/tests/src/expected/CompletionPattern.res.txt b/analysis/tests/src/expected/CompletionPattern.res.txt new file mode 100644 index 000000000..5b97cb151 --- /dev/null +++ b/analysis/tests/src/expected/CompletionPattern.res.txt @@ -0,0 +1,813 @@ +Complete src/CompletionPattern.res 7:13 +posCursor:[7:13] posNoWhite:[7:12] Found expr:[7:3->7:13] +[] + +Complete src/CompletionPattern.res 10:15 +XXX Not found! +Completable: Cpattern Value[v] +[{ + "label": "(_, _, _)", + "kind": 12, + "tags": [], + "detail": "(bool, option, (bool, bool))", + "documentation": null, + "insertText": "(${1:_}, ${2:_}, ${3:_})", + "insertTextFormat": 2 + }] + +Complete src/CompletionPattern.res 13:18 +posCursor:[13:18] posNoWhite:[13:17] Found expr:[13:3->13:24] +posCursor:[13:18] posNoWhite:[13:17] Found pattern:[13:16->13:22] +posCursor:[13:18] posNoWhite:[13:17] Found pattern:[13:17->13:18] +Completable: Cpattern Value[v]=t->tuple($0) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 16:25 +posCursor:[16:25] posNoWhite:[16:24] Found expr:[16:3->16:32] +posCursor:[16:25] posNoWhite:[16:24] Found pattern:[16:16->16:30] +posCursor:[16:25] posNoWhite:[16:24] Found pattern:[16:23->16:29] +posCursor:[16:25] posNoWhite:[16:24] Found pattern:[16:24->16:25] +Completable: Cpattern Value[v]=f->tuple($2), tuple($0) +[{ + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 21:15 +XXX Not found! +Completable: Cpattern Value[x] +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 24:17 +posCursor:[24:17] posNoWhite:[24:16] Found expr:[24:3->24:17] +posCursor:[24:17] posNoWhite:[24:16] Found pattern:[24:16->24:17] +Completable: Cpattern Value[x]=t +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 46:15 +XXX Not found! +Completable: Cpattern Value[f] +[{ + "label": "{}", + "kind": 12, + "tags": [], + "detail": "someRecord", + "documentation": null, + "sortText": "a", + "insertText": "{$0}", + "insertTextFormat": 2 + }] + +Complete src/CompletionPattern.res 49:17 +posCursor:[49:17] posNoWhite:[49:16] Found expr:[49:3->49:19] +posCursor:[49:17] posNoWhite:[49:16] Found pattern:[49:16->49:18] +Completable: Cpattern Value[f]->recordBody +[{ + "label": "first", + "kind": 5, + "tags": [], + "detail": "first: int\n\nsomeRecord", + "documentation": null + }, { + "label": "second", + "kind": 5, + "tags": [], + "detail": "second: (bool, option)\n\nsomeRecord", + "documentation": null + }, { + "label": "optThird", + "kind": 5, + "tags": [], + "detail": "optThird: option<[#second(someRecord) | #first]>\n\nsomeRecord", + "documentation": null + }, { + "label": "nest", + "kind": 5, + "tags": [], + "detail": "nest: nestedRecord\n\nsomeRecord", + "documentation": null + }] + +Complete src/CompletionPattern.res 52:24 +posCursor:[52:24] posNoWhite:[52:22] Found expr:[52:3->52:36] +posCursor:[52:24] posNoWhite:[52:22] Found pattern:[52:16->52:35] +Completable: Cpattern Value[f]->recordBody +[{ + "label": "optThird", + "kind": 5, + "tags": [], + "detail": "optThird: option<[#second(someRecord) | #first]>\n\nsomeRecord", + "documentation": null + }, { + "label": "nest", + "kind": 5, + "tags": [], + "detail": "nest: nestedRecord\n\nsomeRecord", + "documentation": null + }] + +Complete src/CompletionPattern.res 55:19 +posCursor:[55:19] posNoWhite:[55:18] Found expr:[55:3->55:21] +posCursor:[55:19] posNoWhite:[55:18] Found pattern:[55:16->55:20] +posCursor:[55:19] posNoWhite:[55:18] Found pattern:[55:17->55:19] +Completable: Cpattern Value[f]=fi->recordBody +[{ + "label": "first", + "kind": 5, + "tags": [], + "detail": "first: int\n\nsomeRecord", + "documentation": null + }] + +Complete src/CompletionPattern.res 58:19 +posCursor:[58:19] posNoWhite:[58:18] Found expr:[58:3->58:25] +posCursor:[58:19] posNoWhite:[58:18] Found pattern:[58:16->58:24] +posCursor:[58:19] posNoWhite:[58:18] Found pattern:[58:17->58:20] +posCursor:[58:19] posNoWhite:[58:18] Found pattern:[58:18->58:19] +Completable: Cpattern Value[z]=o->tuple($0), recordBody +[{ + "label": "optThird", + "kind": 5, + "tags": [], + "detail": "optThird: option<[#second(someRecord) | #first]>\n\nsomeRecord", + "documentation": null + }] + +Complete src/CompletionPattern.res 61:22 +posCursor:[61:22] posNoWhite:[61:21] Found expr:[61:3->74:1] +posCursor:[61:22] posNoWhite:[61:21] Found pattern:[61:16->61:25] +Completable: Cpattern Value[f]->recordField(nest) +[{ + "label": "{}", + "kind": 12, + "tags": [], + "detail": "nestedRecord", + "documentation": null, + "sortText": "a", + "insertText": "{$0}", + "insertTextFormat": 2 + }] + +Complete src/CompletionPattern.res 64:24 +posCursor:[64:24] posNoWhite:[64:23] Found expr:[64:3->64:27] +posCursor:[64:24] posNoWhite:[64:23] Found pattern:[64:16->64:26] +posCursor:[64:24] posNoWhite:[64:23] Found pattern:[64:23->64:25] +Completable: Cpattern Value[f]->recordField(nest), recordBody +[{ + "label": "nested", + "kind": 5, + "tags": [], + "detail": "nested: bool\n\nnestedRecord", + "documentation": null + }] + +Complete src/CompletionPattern.res 70:22 +posCursor:[70:22] posNoWhite:[70:21] Found expr:[67:8->74:1] +posCursor:[70:22] posNoWhite:[70:21] Found expr:[69:2->72:13] +posCursor:[70:22] posNoWhite:[70:21] Found expr:[70:5->72:13] +posCursor:[70:22] posNoWhite:[70:21] Found expr:[70:5->70:24] +posCursor:[70:22] posNoWhite:[70:21] Found pattern:[70:21->70:23] +Completable: Cpattern Value[nest]->recordBody +[{ + "label": "nested", + "kind": 5, + "tags": [], + "detail": "nested: bool\n\nnestedRecord", + "documentation": null + }] + +Complete src/CompletionPattern.res 76:8 +posCursor:[76:8] posNoWhite:[76:7] Found pattern:[76:7->76:9] +Completable: Cpattern Value[f]->recordBody +[{ + "label": "first", + "kind": 5, + "tags": [], + "detail": "first: int\n\nsomeRecord", + "documentation": null + }, { + "label": "second", + "kind": 5, + "tags": [], + "detail": "second: (bool, option)\n\nsomeRecord", + "documentation": null + }, { + "label": "optThird", + "kind": 5, + "tags": [], + "detail": "optThird: option<[#second(someRecord) | #first]>\n\nsomeRecord", + "documentation": null + }, { + "label": "nest", + "kind": 5, + "tags": [], + "detail": "nest: nestedRecord\n\nsomeRecord", + "documentation": null + }] + +Complete src/CompletionPattern.res 79:16 +posCursor:[79:16] posNoWhite:[79:15] Found pattern:[79:7->79:18] +posCursor:[79:16] posNoWhite:[79:15] Found pattern:[79:14->79:17] +posCursor:[79:16] posNoWhite:[79:15] Found pattern:[79:15->79:16] +Completable: Cpattern Value[f]=n->recordField(nest), recordBody +[{ + "label": "nested", + "kind": 5, + "tags": [], + "detail": "nested: bool\n\nnestedRecord", + "documentation": null + }] + +Complete src/CompletionPattern.res 87:20 +posCursor:[87:20] posNoWhite:[87:19] Found expr:[87:3->87:22] +posCursor:[87:20] posNoWhite:[87:19] Found pattern:[87:16->87:21] +Ppat_construct Two:[87:16->87:19] +posCursor:[87:20] posNoWhite:[87:19] Found pattern:[87:19->87:21] +Ppat_construct ():[87:19->87:21] +Completable: Cpattern Value[z]->variantPayload::Two($0) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 90:21 +posCursor:[90:21] posNoWhite:[90:20] Found expr:[90:3->90:23] +posCursor:[90:21] posNoWhite:[90:20] Found pattern:[90:16->90:22] +Ppat_construct Two:[90:16->90:19] +posCursor:[90:21] posNoWhite:[90:20] Found pattern:[90:20->90:21] +Completable: Cpattern Value[z]=t->variantPayload::Two($0) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 93:23 +posCursor:[93:23] posNoWhite:[93:22] Found expr:[93:3->93:26] +posCursor:[93:23] posNoWhite:[93:22] Found pattern:[93:16->93:25] +Ppat_construct Three:[93:16->93:21] +posCursor:[93:23] posNoWhite:[93:22] Found pattern:[93:22->93:24] +Completable: Cpattern Value[z]->variantPayload::Three($0), recordBody +[{ + "label": "first", + "kind": 5, + "tags": [], + "detail": "first: int\n\nsomeRecord", + "documentation": null + }, { + "label": "second", + "kind": 5, + "tags": [], + "detail": "second: (bool, option)\n\nsomeRecord", + "documentation": null + }, { + "label": "optThird", + "kind": 5, + "tags": [], + "detail": "optThird: option<[#second(someRecord) | #first]>\n\nsomeRecord", + "documentation": null + }, { + "label": "nest", + "kind": 5, + "tags": [], + "detail": "nest: nestedRecord\n\nsomeRecord", + "documentation": null + }] + +Complete src/CompletionPattern.res 96:27 +posCursor:[96:27] posNoWhite:[96:26] Found expr:[96:3->96:29] +posCursor:[96:27] posNoWhite:[96:26] Found pattern:[96:16->96:28] +Ppat_construct Three:[96:16->96:21] +posCursor:[96:27] posNoWhite:[96:26] Found pattern:[96:21->96:29] +posCursor:[96:27] posNoWhite:[96:26] Found pattern:[96:26->96:27] +Completable: Cpattern Value[z]=t->variantPayload::Three($1) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 103:21 +posCursor:[103:21] posNoWhite:[103:20] Found expr:[103:3->103:23] +posCursor:[103:21] posNoWhite:[103:20] Found pattern:[103:16->103:22] +posCursor:[103:21] posNoWhite:[103:20] Found pattern:[103:20->103:21] +Ppat_construct ():[103:20->103:21] +Completable: Cpattern Value[b]->polyvariantPayload::two($0) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 106:22 +posCursor:[106:22] posNoWhite:[106:21] Found expr:[106:3->106:24] +posCursor:[106:22] posNoWhite:[106:21] Found pattern:[106:16->106:23] +posCursor:[106:22] posNoWhite:[106:21] Found pattern:[106:21->106:22] +Completable: Cpattern Value[b]=t->polyvariantPayload::two($0) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 109:24 +posCursor:[109:24] posNoWhite:[109:23] Found expr:[109:3->109:27] +posCursor:[109:24] posNoWhite:[109:23] Found pattern:[109:16->109:26] +posCursor:[109:24] posNoWhite:[109:23] Found pattern:[109:23->109:25] +Completable: Cpattern Value[b]->polyvariantPayload::three($0), recordBody +[{ + "label": "first", + "kind": 5, + "tags": [], + "detail": "first: int\n\nsomeRecord", + "documentation": null + }, { + "label": "second", + "kind": 5, + "tags": [], + "detail": "second: (bool, option)\n\nsomeRecord", + "documentation": null + }, { + "label": "optThird", + "kind": 5, + "tags": [], + "detail": "optThird: option<[#second(someRecord) | #first]>\n\nsomeRecord", + "documentation": null + }, { + "label": "nest", + "kind": 5, + "tags": [], + "detail": "nest: nestedRecord\n\nsomeRecord", + "documentation": null + }] + +Complete src/CompletionPattern.res 112:28 +posCursor:[112:28] posNoWhite:[112:27] Found expr:[112:3->112:30] +posCursor:[112:28] posNoWhite:[112:27] Found pattern:[112:16->112:29] +posCursor:[112:28] posNoWhite:[112:27] Found pattern:[112:22->112:29] +posCursor:[112:28] posNoWhite:[112:27] Found pattern:[112:27->112:28] +Completable: Cpattern Value[b]=t->polyvariantPayload::three($1) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 118:15 +XXX Not found! +Completable: Cpattern Value[c] +[{ + "label": "[]", + "kind": 12, + "tags": [], + "detail": "bool", + "documentation": null, + "sortText": "a", + "insertText": "[$0]", + "insertTextFormat": 2 + }] + +Complete src/CompletionPattern.res 121:17 +posCursor:[121:17] posNoWhite:[121:16] Found expr:[121:3->121:20] +posCursor:[121:17] posNoWhite:[121:16] Found pattern:[121:16->121:18] +Completable: Cpattern Value[c]->array +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 127:21 +posCursor:[127:21] posNoWhite:[127:20] Found expr:[127:3->127:24] +posCursor:[127:21] posNoWhite:[127:20] Found pattern:[127:16->127:22] +Ppat_construct Some:[127:16->127:20] +posCursor:[127:21] posNoWhite:[127:20] Found pattern:[127:20->127:22] +Ppat_construct ():[127:20->127:22] +Completable: Cpattern Value[o]->variantPayload::Some($0) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 134:23 +posCursor:[134:23] posNoWhite:[134:22] Found expr:[134:3->134:26] +posCursor:[134:23] posNoWhite:[134:22] Found pattern:[134:16->134:25] +Ppat_construct Test:[134:16->134:20] +Completable: Cpattern Value[p]->variantPayload::Test($1) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 137:29 +posCursor:[137:29] posNoWhite:[137:28] Found expr:[137:3->137:32] +posCursor:[137:29] posNoWhite:[137:28] Found pattern:[137:16->137:31] +Ppat_construct Test:[137:16->137:20] +posCursor:[137:29] posNoWhite:[137:28] Found pattern:[137:20->137:32] +Completable: Cpattern Value[p]->variantPayload::Test($2) +[{ + "label": "None", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(_)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null, + "insertText": "Some(${1:_})", + "insertTextFormat": 2 + }] + +Complete src/CompletionPattern.res 140:23 +posCursor:[140:23] posNoWhite:[140:22] Found expr:[140:3->140:32] +posCursor:[140:23] posNoWhite:[140:22] Found pattern:[140:16->140:31] +Ppat_construct Test:[140:16->140:20] +posCursor:[140:23] posNoWhite:[140:22] Found pattern:[140:20->140:32] +Completable: Cpattern Value[p]->variantPayload::Test($1) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 143:35 +posCursor:[143:35] posNoWhite:[143:34] Found expr:[143:3->143:38] +posCursor:[143:35] posNoWhite:[143:34] Found pattern:[143:16->143:37] +Ppat_construct Test:[143:16->143:20] +posCursor:[143:35] posNoWhite:[143:34] Found pattern:[143:20->143:38] +Completable: Cpattern Value[p]->variantPayload::Test($3) +[{ + "label": "[]", + "kind": 12, + "tags": [], + "detail": "bool", + "documentation": null, + "sortText": "a", + "insertText": "[$0]", + "insertTextFormat": 2 + }] + +Complete src/CompletionPattern.res 150:24 +posCursor:[150:24] posNoWhite:[150:23] Found expr:[150:3->150:27] +posCursor:[150:24] posNoWhite:[150:23] Found pattern:[150:16->150:26] +Completable: Cpattern Value[v]->polyvariantPayload::test($1) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 153:30 +posCursor:[153:30] posNoWhite:[153:29] Found expr:[153:3->153:33] +posCursor:[153:30] posNoWhite:[153:29] Found pattern:[153:16->153:32] +posCursor:[153:30] posNoWhite:[153:29] Found pattern:[153:21->153:32] +Completable: Cpattern Value[v]->polyvariantPayload::test($2) +[{ + "label": "None", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "Some(_)", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null, + "insertText": "Some(${1:_})", + "insertTextFormat": 2 + }] + +Complete src/CompletionPattern.res 156:24 +posCursor:[156:24] posNoWhite:[156:23] Found expr:[156:3->156:33] +posCursor:[156:24] posNoWhite:[156:23] Found pattern:[156:16->156:32] +posCursor:[156:24] posNoWhite:[156:23] Found pattern:[156:21->156:32] +Completable: Cpattern Value[v]->polyvariantPayload::test($1) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 159:36 +posCursor:[159:36] posNoWhite:[159:35] Found expr:[159:3->159:39] +posCursor:[159:36] posNoWhite:[159:35] Found pattern:[159:16->159:38] +posCursor:[159:36] posNoWhite:[159:35] Found pattern:[159:21->159:38] +Completable: Cpattern Value[v]->polyvariantPayload::test($3) +[{ + "label": "[]", + "kind": 12, + "tags": [], + "detail": "bool", + "documentation": null, + "sortText": "a", + "insertText": "[$0]", + "insertTextFormat": 2 + }] + +Complete src/CompletionPattern.res 164:17 +posCursor:[164:17] posNoWhite:[164:16] Found expr:[164:3->164:20] +posCursor:[164:17] posNoWhite:[164:16] Found pattern:[164:16->164:18] +Ppat_construct ():[164:16->164:18] +Completable: Cpattern Value[s]->tuple($0) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 167:23 +posCursor:[167:23] posNoWhite:[167:21] Found expr:[167:3->167:26] +posCursor:[167:23] posNoWhite:[167:21] Found pattern:[167:16->167:24] +Completable: Cpattern Value[s]->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 + }] + +Complete src/CompletionPattern.res 170:22 +posCursor:[170:22] posNoWhite:[170:21] Found expr:[170:3->170:30] +posCursor:[170:22] posNoWhite:[170:21] Found pattern:[170:16->170:28] +Completable: Cpattern Value[s]->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 + }] + +Complete src/CompletionPattern.res 173:35 +XXX Not found! +Completable: Cpattern Value[s] +[{ + "label": "(_, _, _)", + "kind": 12, + "tags": [], + "detail": "(bool, option, array)", + "documentation": null, + "insertText": "(${1:_}, ${2:_}, ${3:_})", + "insertTextFormat": 2 + }] + +Complete src/CompletionPattern.res 176:41 +posCursor:[176:41] posNoWhite:[176:40] Found expr:[176:3->176:50] +posCursor:[176:41] posNoWhite:[176:40] Found pattern:[176:35->176:47] +Completable: Cpattern Value[s]->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 + }] + +Complete src/CompletionPattern.res 179:21 +XXX Not found! +Completable: Cpattern Value[z] +[{ + "label": "One", + "kind": 4, + "tags": [], + "detail": "One\n\ntype someVariant = One | Two(bool) | Three(someRecord, bool)", + "documentation": null, + "insertText": "One", + "insertTextFormat": 2 + }, { + "label": "Two(_)", + "kind": 4, + "tags": [], + "detail": "Two(bool)\n\ntype someVariant = One | Two(bool) | Three(someRecord, bool)", + "documentation": null, + "insertText": "Two(${1:_})", + "insertTextFormat": 2 + }, { + "label": "Three(_, _)", + "kind": 4, + "tags": [], + "detail": "Three(someRecord, bool)\n\ntype someVariant = One | Two(bool) | Three(someRecord, bool)", + "documentation": null, + "insertText": "Three(${1:_}, ${2:_})", + "insertTextFormat": 2 + }] + +Complete src/CompletionPattern.res 182:32 +posCursor:[182:32] posNoWhite:[182:31] Found expr:[182:3->182:37] +posCursor:[182:32] posNoWhite:[182:31] Found pattern:[182:16->182:34] +posCursor:[182:32] posNoWhite:[182:31] Found pattern:[182:22->182:34] +Ppat_construct Two:[182:22->182:25] +Completable: Cpattern Value[z]->variantPayload::Two($0) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 185:48 +posCursor:[185:48] posNoWhite:[185:47] Found expr:[185:3->185:53] +posCursor:[185:48] posNoWhite:[185:47] Found pattern:[185:16->185:50] +posCursor:[185:48] posNoWhite:[185:47] Found pattern:[185:22->185:50] +Ppat_construct Three:[185:22->185:27] +posCursor:[185:48] posNoWhite:[185:47] Found pattern:[185:27->185:53] +Completable: Cpattern Value[z]->variantPayload::Three($1) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 188:34 +posCursor:[188:34] posNoWhite:[188:33] Found expr:[188:3->188:39] +posCursor:[188:34] posNoWhite:[188:33] Found pattern:[188:16->188:36] +posCursor:[188:34] posNoWhite:[188:33] Found pattern:[188:23->188:36] +Completable: Cpattern Value[b]->polyvariantPayload::two($0) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] + +Complete src/CompletionPattern.res 191:50 +posCursor:[191:50] posNoWhite:[191:49] Found expr:[191:3->191:55] +posCursor:[191:50] posNoWhite:[191:49] Found pattern:[191:16->191:52] +posCursor:[191:50] posNoWhite:[191:49] Found pattern:[191:23->191:52] +posCursor:[191:50] posNoWhite:[191:49] Found pattern:[191:29->191:52] +Completable: Cpattern Value[b]->polyvariantPayload::three($1) +[{ + "label": "true", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }, { + "label": "false", + "kind": 4, + "tags": [], + "detail": "bool", + "documentation": null + }] +