Skip to content

Commit bcc0272

Browse files
committedJan 5, 2023
handle ppat_or
1 parent a8f65f9 commit bcc0272

File tree

4 files changed

+236
-217
lines changed

4 files changed

+236
-217
lines changed
 

‎analysis/src/CompletionFrontEnd.ml

+200-217
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,9 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
397397

398398
let lookingForPat = ref None in
399399

400+
let locHasCursor = CursorPosition.locHasCursor ~pos:posBeforeCursor in
401+
let locIsEmpty = CursorPosition.locIsEmpty ~pos:posBeforeCursor in
402+
400403
let rec traverseTupleItems tupleItems ~nextPatternPath ~resultFromFoundItemNum
401404
=
402405
let itemNum = ref (-1) in
@@ -418,219 +421,200 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
418421
if !posNum > -1 then Some ("", resultFromFoundItemNum !posNum) else None
419422
| v, _ -> v
420423
and traversePattern (pat : Parsetree.pattern) ~patternPath =
421-
if
422-
pat.ppat_loc
423-
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
424-
= HasCursor
425-
then
426-
match pat.ppat_desc with
427-
| Ppat_any | Ppat_constant _ | Ppat_interval _ -> None
428-
| Ppat_lazy p
429-
| Ppat_constraint (p, _)
430-
| Ppat_alias (p, _)
431-
| Ppat_exception p
432-
| Ppat_open (_, p) ->
433-
p |> traversePattern ~patternPath
434-
| Ppat_or (p1, p2) ->
424+
let someIfHasCursor v =
425+
if locHasCursor pat.Parsetree.ppat_loc then Some v else None
426+
in
427+
match pat.ppat_desc with
428+
| Ppat_any | Ppat_constant _ | Ppat_interval _ -> None
429+
| Ppat_lazy p
430+
| Ppat_constraint (p, _)
431+
| Ppat_alias (p, _)
432+
| Ppat_exception p
433+
| Ppat_open (_, p) ->
434+
p |> traversePattern ~patternPath
435+
| Ppat_or (p1, p2) -> (
436+
let orPatWithItem =
435437
[p1; p2] |> List.find_map (fun p -> p |> traversePattern ~patternPath)
436-
| Ppat_var {txt} -> Some (txt, patternPath)
437-
| Ppat_construct ({txt = Lident "()"}, None) ->
438-
(* switch s { | (<com>) }*)
439-
Some ("", patternPath @ [Completable.PTupleItem {itemNum = 0}])
440-
| Ppat_construct ({txt = Lident prefix}, None) ->
441-
Some (prefix, patternPath)
442-
| Ppat_variant (prefix, None) -> Some ("#" ^ prefix, patternPath)
443-
| Ppat_array arrayPatterns ->
444-
let nextPatternPath = [Completable.PArray] @ patternPath in
445-
if List.length arrayPatterns = 0 then Some ("", nextPatternPath)
446-
else
447-
arrayPatterns
448-
|> List.find_map (fun pat ->
449-
pat |> traversePattern ~patternPath:nextPatternPath)
450-
| Ppat_tuple tupleItems ->
451-
tupleItems
452-
|> traverseTupleItems
453-
~nextPatternPath:(fun itemNum ->
454-
[Completable.PTupleItem {itemNum}] @ patternPath)
455-
~resultFromFoundItemNum:(fun itemNum ->
456-
[Completable.PTupleItem {itemNum = itemNum + 1}] @ patternPath)
457-
| Ppat_record ([], _) ->
458-
(* Empty fields means we're in a record body `{}`. Complete for the fields. *)
459-
Some ("", [Completable.PRecordBody {seenFields = []}] @ patternPath)
460-
| Ppat_record (fields, _) -> (
461-
let fieldWithCursor = ref None in
462-
let fieldWithPatHole = ref None in
438+
in
439+
match orPatWithItem with
440+
| None when isPatternHole p1 || isPatternHole p2 -> Some ("", patternPath)
441+
| v -> v)
442+
| Ppat_var {txt} -> someIfHasCursor (txt, patternPath)
443+
| Ppat_construct ({txt = Lident "()"}, None) ->
444+
(* switch s { | (<com>) }*)
445+
someIfHasCursor ("", patternPath @ [Completable.PTupleItem {itemNum = 0}])
446+
| Ppat_construct ({txt = Lident prefix}, None) ->
447+
someIfHasCursor (prefix, patternPath)
448+
| Ppat_variant (prefix, None) -> someIfHasCursor ("#" ^ prefix, patternPath)
449+
| Ppat_array arrayPatterns ->
450+
let nextPatternPath = [Completable.PArray] @ patternPath in
451+
if List.length arrayPatterns = 0 && locHasCursor pat.ppat_loc then
452+
Some ("", nextPatternPath)
453+
else
454+
arrayPatterns
455+
|> List.find_map (fun pat ->
456+
pat |> traversePattern ~patternPath:nextPatternPath)
457+
| Ppat_tuple tupleItems when locHasCursor pat.ppat_loc ->
458+
tupleItems
459+
|> traverseTupleItems
460+
~nextPatternPath:(fun itemNum ->
461+
[Completable.PTupleItem {itemNum}] @ patternPath)
462+
~resultFromFoundItemNum:(fun itemNum ->
463+
[Completable.PTupleItem {itemNum = itemNum + 1}] @ patternPath)
464+
| Ppat_record ([], _) ->
465+
(* Empty fields means we're in a record body `{}`. Complete for the fields. *)
466+
someIfHasCursor
467+
("", [Completable.PRecordBody {seenFields = []}] @ patternPath)
468+
| Ppat_record (fields, _) -> (
469+
let fieldWithCursor = ref None in
470+
let fieldWithPatHole = ref None in
471+
fields
472+
|> List.iter (fun (fname, f) ->
473+
match
474+
( fname.Location.txt,
475+
f.Parsetree.ppat_loc
476+
|> CursorPosition.classifyLoc ~pos:posBeforeCursor )
477+
with
478+
| Longident.Lident fname, HasCursor ->
479+
fieldWithCursor := Some (fname, f)
480+
| Lident fname, _ when isPatternHole f ->
481+
fieldWithPatHole := Some (fname, f)
482+
| _ -> ());
483+
let seenFields =
463484
fields
464-
|> List.iter (fun (fname, f) ->
465-
match
466-
( fname.Location.txt,
467-
f.Parsetree.ppat_loc
468-
|> CursorPosition.classifyLoc ~pos:posBeforeCursor )
469-
with
470-
| Longident.Lident fname, HasCursor ->
471-
fieldWithCursor := Some (fname, f)
472-
| Lident fname, _ when isPatternHole f ->
473-
fieldWithPatHole := Some (fname, f)
474-
| _ -> ());
475-
let seenFields =
476-
fields
477-
|> List.filter_map (fun (fieldName, _f) ->
478-
match fieldName with
479-
| {Location.txt = Longident.Lident fieldName} -> Some fieldName
480-
| _ -> None)
481-
in
482-
match (!fieldWithCursor, !fieldWithPatHole) with
483-
| Some (fname, f), _ | None, Some (fname, f) -> (
484-
match f.ppat_desc with
485-
| Ppat_record _ | Ppat_construct _ | Ppat_variant _ | Ppat_tuple _ ->
486-
(* These are things we can continue into in the pattern. *)
487-
f
488-
|> traversePattern
489-
~patternPath:
490-
([Completable.PFollowRecordField {fieldName = fname}]
491-
@ patternPath)
492-
| Ppat_extension ({txt = "rescript.patternhole"}, _) ->
493-
(* A pattern hole means for example `{someField: <com>}`. We want to complete for the type of `someField`. *)
494-
Some
495-
( "",
496-
[Completable.PFollowRecordField {fieldName = fname}]
497-
@ patternPath )
498-
| Ppat_var {txt} ->
499-
(* A var means `{s}` or similar. Complete for fields. *)
500-
Some (txt, [Completable.PRecordBody {seenFields}] @ patternPath)
501-
| _ -> None)
502-
| None, None -> (
503-
(* Figure out if we're completing for a new field.
504-
If the cursor is inside of the record body, but no field has the cursor,
505-
and there's no pattern hole. Check the first char to the left of the cursor,
506-
ignoring white space. If that's a comma, we assume you're completing for a new field. *)
507-
match firstCharBeforeCursorNoWhite with
508-
| Some ',' ->
509-
Some ("", [Completable.PRecordBody {seenFields}] @ patternPath)
510-
| _ -> None))
511-
| Ppat_construct
512-
( {txt},
513-
Some {ppat_loc; ppat_desc = Ppat_construct ({txt = Lident "()"}, _)}
514-
)
515-
when ppat_loc
516-
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
517-
= HasCursor ->
518-
(* Empty payload with cursor, like: Test(<com>) *)
519-
Some
520-
( "",
521-
[
522-
Completable.PVariantPayload
523-
{constructorName = getUnqualifiedName txt; itemNum = 0};
524-
]
525-
@ patternPath )
526-
| Ppat_construct ({txt}, Some pat)
527-
when posBeforeCursor >= (pat.ppat_loc |> Loc.end_)
528-
&& firstCharBeforeCursorNoWhite = Some ','
529-
&& isPatternTuple pat = false ->
530-
(* Empty payload with trailing ',', like: Test(true, <com>) *)
531-
Some
532-
( "",
533-
[
534-
Completable.PVariantPayload
535-
{constructorName = getUnqualifiedName txt; itemNum = 1};
536-
]
537-
@ patternPath )
538-
| Ppat_construct ({txt}, Some pat)
539-
when pat.ppat_loc
540-
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
541-
= HasCursor
542-
&& isPatternTuple pat = false ->
543-
(* Single payload *)
544-
pat
545-
|> traversePattern
546-
~patternPath:
547-
([
548-
Completable.PVariantPayload
549-
{constructorName = getUnqualifiedName txt; itemNum = 0};
550-
]
551-
@ patternPath)
552-
| Ppat_construct
553-
({txt}, Some {ppat_loc; ppat_desc = Ppat_tuple tupleItems})
554-
when ppat_loc
555-
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
556-
= HasCursor ->
557-
tupleItems
558-
|> traverseTupleItems
559-
~nextPatternPath:(fun itemNum ->
560-
[
561-
Completable.PVariantPayload
562-
{constructorName = getUnqualifiedName txt; itemNum};
563-
]
564-
@ patternPath)
565-
~resultFromFoundItemNum:(fun itemNum ->
566-
[
567-
Completable.PVariantPayload
568-
{
569-
constructorName = getUnqualifiedName txt;
570-
itemNum = itemNum + 1;
571-
};
572-
]
573-
@ patternPath)
574-
| Ppat_variant
575-
( txt,
576-
Some {ppat_loc; ppat_desc = Ppat_construct ({txt = Lident "()"}, _)}
577-
)
578-
when ppat_loc
579-
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
580-
= HasCursor ->
581-
(* Empty payload with cursor, like: #test(<com>) *)
582-
Some
583-
( "",
584-
[
585-
Completable.PPolyvariantPayload
586-
{constructorName = txt; itemNum = 0};
587-
]
588-
@ patternPath )
589-
| Ppat_variant (txt, Some pat)
590-
when posBeforeCursor >= (pat.ppat_loc |> Loc.end_)
591-
&& firstCharBeforeCursorNoWhite = Some ','
592-
&& isPatternTuple pat = false ->
593-
(* Empty payload with trailing ',', like: #test(true, <com>) *)
594-
Some
595-
( "",
596-
[
597-
Completable.PPolyvariantPayload
598-
{constructorName = txt; itemNum = 1};
599-
]
600-
@ patternPath )
601-
| Ppat_variant (txt, Some pat)
602-
when pat.ppat_loc
603-
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
604-
= HasCursor
605-
&& isPatternTuple pat = false ->
606-
(* Single payload *)
607-
pat
608-
|> traversePattern
609-
~patternPath:
610-
([
611-
Completable.PPolyvariantPayload
612-
{constructorName = txt; itemNum = 0};
613-
]
614-
@ patternPath)
615-
| Ppat_variant (txt, Some {ppat_loc; ppat_desc = Ppat_tuple tupleItems})
616-
when ppat_loc
617-
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
618-
= HasCursor ->
619-
tupleItems
620-
|> traverseTupleItems
621-
~nextPatternPath:(fun itemNum ->
622-
[
623-
Completable.PPolyvariantPayload {constructorName = txt; itemNum};
624-
]
625-
@ patternPath)
626-
~resultFromFoundItemNum:(fun itemNum ->
627-
[
628-
Completable.PPolyvariantPayload
629-
{constructorName = txt; itemNum = itemNum + 1};
630-
]
631-
@ patternPath)
632-
| _ -> None
633-
else None
485+
|> List.filter_map (fun (fieldName, _f) ->
486+
match fieldName with
487+
| {Location.txt = Longident.Lident fieldName} -> Some fieldName
488+
| _ -> None)
489+
in
490+
match (!fieldWithCursor, !fieldWithPatHole) with
491+
| Some (fname, f), _ | None, Some (fname, f) -> (
492+
match f.ppat_desc with
493+
| Ppat_extension ({txt = "rescript.patternhole"}, _) ->
494+
(* A pattern hole means for example `{someField: <com>}`. We want to complete for the type of `someField`. *)
495+
someIfHasCursor
496+
( "",
497+
[Completable.PFollowRecordField {fieldName = fname}] @ patternPath
498+
)
499+
| Ppat_var {txt} ->
500+
(* A var means `{s}` or similar. Complete for fields. *)
501+
someIfHasCursor
502+
(txt, [Completable.PRecordBody {seenFields}] @ patternPath)
503+
| _ ->
504+
f
505+
|> traversePattern
506+
~patternPath:
507+
([Completable.PFollowRecordField {fieldName = fname}]
508+
@ patternPath))
509+
| None, None -> (
510+
(* Figure out if we're completing for a new field.
511+
If the cursor is inside of the record body, but no field has the cursor,
512+
and there's no pattern hole. Check the first char to the left of the cursor,
513+
ignoring white space. If that's a comma, we assume you're completing for a new field. *)
514+
match firstCharBeforeCursorNoWhite with
515+
| Some ',' ->
516+
someIfHasCursor
517+
("", [Completable.PRecordBody {seenFields}] @ patternPath)
518+
| _ -> None))
519+
| Ppat_construct
520+
( {txt},
521+
Some {ppat_loc; ppat_desc = Ppat_construct ({txt = Lident "()"}, _)}
522+
)
523+
when locHasCursor ppat_loc ->
524+
(* Empty payload with cursor, like: Test(<com>) *)
525+
Some
526+
( "",
527+
[
528+
Completable.PVariantPayload
529+
{constructorName = getUnqualifiedName txt; itemNum = 0};
530+
]
531+
@ patternPath )
532+
| Ppat_construct ({txt}, Some pat)
533+
when posBeforeCursor >= (pat.ppat_loc |> Loc.end_)
534+
&& firstCharBeforeCursorNoWhite = Some ','
535+
&& isPatternTuple pat = false ->
536+
(* Empty payload with trailing ',', like: Test(true, <com>) *)
537+
Some
538+
( "",
539+
[
540+
Completable.PVariantPayload
541+
{constructorName = getUnqualifiedName txt; itemNum = 1};
542+
]
543+
@ patternPath )
544+
| Ppat_construct ({txt}, Some pat)
545+
when locHasCursor pat.ppat_loc && isPatternTuple pat = false ->
546+
(* Single payload *)
547+
pat
548+
|> traversePattern
549+
~patternPath:
550+
([
551+
Completable.PVariantPayload
552+
{constructorName = getUnqualifiedName txt; itemNum = 0};
553+
]
554+
@ patternPath)
555+
| Ppat_construct ({txt}, Some {ppat_loc; ppat_desc = Ppat_tuple tupleItems})
556+
when locHasCursor ppat_loc ->
557+
tupleItems
558+
|> traverseTupleItems
559+
~nextPatternPath:(fun itemNum ->
560+
[
561+
Completable.PVariantPayload
562+
{constructorName = getUnqualifiedName txt; itemNum};
563+
]
564+
@ patternPath)
565+
~resultFromFoundItemNum:(fun itemNum ->
566+
[
567+
Completable.PVariantPayload
568+
{
569+
constructorName = getUnqualifiedName txt;
570+
itemNum = itemNum + 1;
571+
};
572+
]
573+
@ patternPath)
574+
| Ppat_variant
575+
( txt,
576+
Some {ppat_loc; ppat_desc = Ppat_construct ({txt = Lident "()"}, _)}
577+
)
578+
when locHasCursor ppat_loc ->
579+
(* Empty payload with cursor, like: #test(<com>) *)
580+
Some
581+
( "",
582+
[Completable.PPolyvariantPayload {constructorName = txt; itemNum = 0}]
583+
@ patternPath )
584+
| Ppat_variant (txt, Some pat)
585+
when posBeforeCursor >= (pat.ppat_loc |> Loc.end_)
586+
&& firstCharBeforeCursorNoWhite = Some ','
587+
&& isPatternTuple pat = false ->
588+
(* Empty payload with trailing ',', like: #test(true, <com>) *)
589+
Some
590+
( "",
591+
[Completable.PPolyvariantPayload {constructorName = txt; itemNum = 1}]
592+
@ patternPath )
593+
| Ppat_variant (txt, Some pat)
594+
when locHasCursor pat.ppat_loc && isPatternTuple pat = false ->
595+
(* Single payload *)
596+
pat
597+
|> traversePattern
598+
~patternPath:
599+
([
600+
Completable.PPolyvariantPayload
601+
{constructorName = txt; itemNum = 0};
602+
]
603+
@ patternPath)
604+
| Ppat_variant (txt, Some {ppat_loc; ppat_desc = Ppat_tuple tupleItems})
605+
when locHasCursor ppat_loc ->
606+
tupleItems
607+
|> traverseTupleItems
608+
~nextPatternPath:(fun itemNum ->
609+
[Completable.PPolyvariantPayload {constructorName = txt; itemNum}]
610+
@ patternPath)
611+
~resultFromFoundItemNum:(fun itemNum ->
612+
[
613+
Completable.PPolyvariantPayload
614+
{constructorName = txt; itemNum = itemNum + 1};
615+
]
616+
@ patternPath)
617+
| _ -> None
634618
in
635619
let completePattern (pat : Parsetree.pattern) =
636620
match (pat |> traversePattern ~patternPath:[], !lookingForPat) with
@@ -714,17 +698,16 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
714698
let hasCaseWithCursor =
715699
cases
716700
|> List.find_opt (fun case ->
717-
case.Parsetree.pc_lhs.ppat_loc
718-
|> CursorPosition.classifyLoc ~pos:posBeforeCursor
719-
= HasCursor)
701+
locHasCursor case.Parsetree.pc_lhs.ppat_loc)
720702
|> Option.is_some
721703
in
722-
let hasCaseWithPatHole =
704+
let hasCaseWithEmptyLoc =
723705
cases
724-
|> List.find_opt (fun case -> isPatternHole case.Parsetree.pc_lhs)
706+
|> List.find_opt (fun case ->
707+
locIsEmpty case.Parsetree.pc_lhs.ppat_loc)
725708
|> Option.is_some
726709
in
727-
match (hasCaseWithPatHole, hasCaseWithCursor) with
710+
match (hasCaseWithEmptyLoc, hasCaseWithCursor) with
728711
| _, true ->
729712
(* Always continue if there's a case with the cursor *)
730713
setLookingForPat ctxPath

‎analysis/src/SharedTypes.ml

+4
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,10 @@ module CursorPosition = struct
699699
if posStart <= pos && pos <= posEnd then HasCursor
700700
else if posEnd = (Location.none |> Loc.end_) then EmptyLoc
701701
else NoCursor
702+
703+
let locHasCursor loc ~pos = loc |> classifyLoc ~pos = HasCursor
704+
705+
let locIsEmpty loc ~pos = loc |> classifyLoc ~pos = EmptyLoc
702706
end
703707

704708
type labelled = {

‎analysis/tests/src/CompletionPattern.res

+3
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,6 @@ let s = (true, Some(true), [false])
176176

177177
// switch s { | (true, []) => () | (true, , []) }
178178
// ^com
179+
180+
// switch z { | One | }
181+
// ^com

‎analysis/tests/src/expected/CompletionPattern.res.txt

+29
Original file line numberDiff line numberDiff line change
@@ -723,3 +723,32 @@ Completable: Cpattern Value[s]->tuple($1)
723723
"insertTextFormat": 2
724724
}]
725725

726+
Complete src/CompletionPattern.res 179:21
727+
XXX Not found!
728+
Completable: Cpattern Value[z]
729+
[{
730+
"label": "One",
731+
"kind": 4,
732+
"tags": [],
733+
"detail": "One\n\ntype someVariant = One | Two(bool) | Three(someRecord, bool)",
734+
"documentation": null,
735+
"insertText": "One",
736+
"insertTextFormat": 2
737+
}, {
738+
"label": "Two(_)",
739+
"kind": 4,
740+
"tags": [],
741+
"detail": "Two(bool)\n\ntype someVariant = One | Two(bool) | Three(someRecord, bool)",
742+
"documentation": null,
743+
"insertText": "Two(${1:_})",
744+
"insertTextFormat": 2
745+
}, {
746+
"label": "Three(_, _)",
747+
"kind": 4,
748+
"tags": [],
749+
"detail": "Three(someRecord, bool)\n\ntype someVariant = One | Two(bool) | Three(someRecord, bool)",
750+
"documentation": null,
751+
"insertText": "Three(${1:_}, ${2:_})",
752+
"insertTextFormat": 2
753+
}]
754+

0 commit comments

Comments
 (0)
Please sign in to comment.