From ebb77391544c47f1c3543255034dce0841173a24 Mon Sep 17 00:00:00 2001
From: Gabriel Nordeborn <gabbe.nord@gmail.com>
Date: Sun, 26 May 2024 12:50:43 +0200
Subject: [PATCH 1/2] handle ppat_or and guards in code action for expanding
 catch alls

---
 analysis/src/Xform.ml                     |  54 ++++------
 analysis/tests/src/Xform.res              |  37 +++++++
 analysis/tests/src/expected/Xform.res.txt | 120 ++++++++++++++++++++--
 3 files changed, 169 insertions(+), 42 deletions(-)

diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml
index 6e1673573..0ac5e89e3 100644
--- a/analysis/src/Xform.ml
+++ b/analysis/src/Xform.ml
@@ -392,15 +392,28 @@ module ExpandCatchAllForVariants = struct
       if Debug.verbose () then
         print_endline
           "[codeAction - ExpandCatchAllForVariants] Found target switch";
-      let currentConstructorNames =
+      let rec findAllConstructorNames ?(mode : [`option | `default] = `default)
+          ?(constructorNames = []) (p : Parsetree.pattern) =
+        match p.ppat_desc with
+        | Ppat_construct ({txt = Lident "Some"}, Some payload)
+          when mode = `option ->
+          findAllConstructorNames ~mode ~constructorNames payload
+        | Ppat_construct ({txt}, _) -> Longident.last txt :: constructorNames
+        | Ppat_variant (name, _) -> name :: constructorNames
+        | Ppat_or (a, b) ->
+          findAllConstructorNames ~mode ~constructorNames a
+          @ findAllConstructorNames ~mode ~constructorNames b
+          @ constructorNames
+        | _ -> constructorNames
+      in
+      let getCurrentConstructorNames cases =
         cases
-        |> List.filter_map (fun (c : Parsetree.case) ->
-               match c with
-               | {pc_lhs = {ppat_desc = Ppat_construct ({txt}, _)}} ->
-                 Some (Longident.last txt)
-               | {pc_lhs = {ppat_desc = Ppat_variant (name, _)}} -> Some name
-               | _ -> None)
+        |> List.map (fun (c : Parsetree.case) ->
+               if Option.is_some c.pc_guard then []
+               else findAllConstructorNames ~mode:`option c.pc_lhs)
+        |> List.flatten
       in
+      let currentConstructorNames = getCurrentConstructorNames cases in
       match
         switchExpr
         |> extractTypeFromExpr ~debug ~path ~currentFile ~full
@@ -468,32 +481,7 @@ module ExpandCatchAllForVariants = struct
         in
         match innerType with
         | Some ((Tvariant _ | Tpolyvariant _) as variant) ->
-          let currentConstructorNames =
-            cases
-            |> List.filter_map (fun (c : Parsetree.case) ->
-                   match c with
-                   | {
-                    pc_lhs =
-                      {
-                        ppat_desc =
-                          Ppat_construct
-                            ( {txt = Lident "Some"},
-                              Some {ppat_desc = Ppat_construct ({txt}, _)} );
-                      };
-                   } ->
-                     Some (Longident.last txt)
-                   | {
-                    pc_lhs =
-                      {
-                        ppat_desc =
-                          Ppat_construct
-                            ( {txt = Lident "Some"},
-                              Some {ppat_desc = Ppat_variant (name, _)} );
-                      };
-                   } ->
-                     Some name
-                   | _ -> None)
-          in
+          let currentConstructorNames = getCurrentConstructorNames cases in
           let hasNoneCase =
             cases
             |> List.exists (fun (c : Parsetree.case) ->
diff --git a/analysis/tests/src/Xform.res b/analysis/tests/src/Xform.res
index 89b8ea8e4..fcd287ea0 100644
--- a/analysis/tests/src/Xform.res
+++ b/analysis/tests/src/Xform.res
@@ -82,6 +82,19 @@ let _x = switch variant {
 //  ^xfm
 }
 
+let _x = switch variant {
+| First | Second => "first"
+| _ => "other"
+//  ^xfm
+}
+
+let _x = switch variant {
+| First if 1 > 2 => "first"
+| Second => "second"
+| _ => "other"
+//  ^xfm
+}
+
 let polyvariant: [#first | #second | #"illegal identifier" | #third(int)] = #first
 
 let _y = switch polyvariant {
@@ -90,6 +103,12 @@ let _y = switch polyvariant {
 //  ^xfm
 }
 
+let _y = switch polyvariant {
+| #first | #second => "first"
+| _ => "other"
+//  ^xfm
+}
+
 let variantOpt = Some(variant)
 
 let _x = switch variantOpt {
@@ -98,6 +117,18 @@ let _x = switch variantOpt {
 //  ^xfm
 }
 
+let _x = switch variantOpt {
+| Some(First) | Some(Second) => "first"
+| _ => "other"
+//  ^xfm
+}
+
+let _x = switch variantOpt {
+| Some(First | Second) => "first"
+| _ => "other"
+//  ^xfm
+}
+
 let polyvariantOpt = Some(polyvariant)
 
 let _x = switch polyvariantOpt {
@@ -106,3 +137,9 @@ let _x = switch polyvariantOpt {
 | _ => "other"
 //  ^xfm
 }
+
+let _x = switch polyvariantOpt {
+| Some(#first | #second) => "first"
+| _ => "other"
+//  ^xfm
+}
diff --git a/analysis/tests/src/expected/Xform.res.txt b/analysis/tests/src/expected/Xform.res.txt
index 6db9e6286..2b38ddf41 100644
--- a/analysis/tests/src/expected/Xform.res.txt
+++ b/analysis/tests/src/expected/Xform.res.txt
@@ -206,8 +206,42 @@ newText:
   <--here
   Second | Third | Fourth(_)
 
-Xform src/Xform.res 88:4
-posCursor:[86:16] posNoWhite:[86:14] Found expr:[86:9->90:1]
+Xform src/Xform.res 86:4
+posCursor:[84:16] posNoWhite:[84:14] Found expr:[84:9->88:1]
+Completable: Cpath Value[variant]
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 pervasives
+ContextPath Value[variant]
+Path variant
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 pervasives
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 86, "character": 2}, "end": {"line": 86, "character": 3}}
+newText:
+  <--here
+  Third | Fourth(_)
+
+Xform src/Xform.res 93:4
+posCursor:[90:16] posNoWhite:[90:14] Found expr:[90:9->95:1]
+Completable: Cpath Value[variant]
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 pervasives
+ContextPath Value[variant]
+Path variant
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 pervasives
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 93, "character": 2}, "end": {"line": 93, "character": 3}}
+newText:
+  <--here
+  First | Third | Fourth(_)
+
+Xform src/Xform.res 101:4
+posCursor:[99:16] posNoWhite:[99:14] Found expr:[99:9->103:1]
 Completable: Cpath Value[polyvariant]
 Package opens Pervasives.JsxModules.place holder
 Resolved opens 1 pervasives
@@ -218,13 +252,30 @@ Resolved opens 1 pervasives
 Hit: Expand catch-all
 
 TextDocumentEdit: Xform.res
-{"start": {"line": 88, "character": 2}, "end": {"line": 88, "character": 3}}
+{"start": {"line": 101, "character": 2}, "end": {"line": 101, "character": 3}}
 newText:
   <--here
   #second | #"illegal identifier" | #third(_)
 
-Xform src/Xform.res 96:4
-posCursor:[94:16] posNoWhite:[94:14] Found expr:[94:9->98:1]
+Xform src/Xform.res 107:4
+posCursor:[105:16] posNoWhite:[105:14] Found expr:[105:9->109:1]
+Completable: Cpath Value[polyvariant]
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 pervasives
+ContextPath Value[polyvariant]
+Path polyvariant
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 pervasives
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 107, "character": 2}, "end": {"line": 107, "character": 3}}
+newText:
+  <--here
+  #"illegal identifier" | #third(_)
+
+Xform src/Xform.res 115:4
+posCursor:[113:16] posNoWhite:[113:14] Found expr:[113:9->117:1]
 Completable: Cpath Value[variantOpt]
 Package opens Pervasives.JsxModules.place holder
 Resolved opens 1 pervasives
@@ -235,13 +286,47 @@ Resolved opens 1 pervasives
 Hit: Expand catch-all
 
 TextDocumentEdit: Xform.res
-{"start": {"line": 96, "character": 2}, "end": {"line": 96, "character": 3}}
+{"start": {"line": 115, "character": 2}, "end": {"line": 115, "character": 3}}
 newText:
   <--here
   Some(Second | Third | Fourth(_)) | None
 
-Xform src/Xform.res 105:4
-posCursor:[102:16] posNoWhite:[102:14] Found expr:[102:9->107:1]
+Xform src/Xform.res 121:4
+posCursor:[119:16] posNoWhite:[119:14] Found expr:[119:9->123:1]
+Completable: Cpath Value[variantOpt]
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 pervasives
+ContextPath Value[variantOpt]
+Path variantOpt
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 pervasives
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 121, "character": 2}, "end": {"line": 121, "character": 3}}
+newText:
+  <--here
+  Some(Third | Fourth(_)) | None
+
+Xform src/Xform.res 127:4
+posCursor:[125:16] posNoWhite:[125:14] Found expr:[125:9->129:1]
+Completable: Cpath Value[variantOpt]
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 pervasives
+ContextPath Value[variantOpt]
+Path variantOpt
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 pervasives
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 127, "character": 2}, "end": {"line": 127, "character": 3}}
+newText:
+  <--here
+  Some(Third | Fourth(_)) | None
+
+Xform src/Xform.res 136:4
+posCursor:[133:16] posNoWhite:[133:14] Found expr:[133:9->138:1]
 Completable: Cpath Value[polyvariantOpt]
 Package opens Pervasives.JsxModules.place holder
 Resolved opens 1 pervasives
@@ -252,8 +337,25 @@ Resolved opens 1 pervasives
 Hit: Expand catch-all
 
 TextDocumentEdit: Xform.res
-{"start": {"line": 105, "character": 2}, "end": {"line": 105, "character": 3}}
+{"start": {"line": 136, "character": 2}, "end": {"line": 136, "character": 3}}
 newText:
   <--here
   Some(#"illegal identifier" | #second | #third(_))
 
+Xform src/Xform.res 142:4
+posCursor:[140:16] posNoWhite:[140:14] Found expr:[140:9->144:1]
+Completable: Cpath Value[polyvariantOpt]
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 pervasives
+ContextPath Value[polyvariantOpt]
+Path polyvariantOpt
+Package opens Pervasives.JsxModules.place holder
+Resolved opens 1 pervasives
+Hit: Expand catch-all
+
+TextDocumentEdit: Xform.res
+{"start": {"line": 142, "character": 2}, "end": {"line": 142, "character": 3}}
+newText:
+  <--here
+  Some(#"illegal identifier" | #third(_)) | None
+

From 1636515f3d91e48d26d1da9670c79bc6e77642cb Mon Sep 17 00:00:00 2001
From: Gabriel Nordeborn <gabbe.nord@gmail.com>
Date: Sun, 26 May 2024 12:53:11 +0200
Subject: [PATCH 2/2] set mode properly

---
 analysis/src/Xform.ml | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml
index 0ac5e89e3..cc0cb63fb 100644
--- a/analysis/src/Xform.ml
+++ b/analysis/src/Xform.ml
@@ -406,11 +406,11 @@ module ExpandCatchAllForVariants = struct
           @ constructorNames
         | _ -> constructorNames
       in
-      let getCurrentConstructorNames cases =
+      let getCurrentConstructorNames ?mode cases =
         cases
         |> List.map (fun (c : Parsetree.case) ->
                if Option.is_some c.pc_guard then []
-               else findAllConstructorNames ~mode:`option c.pc_lhs)
+               else findAllConstructorNames ?mode c.pc_lhs)
         |> List.flatten
       in
       let currentConstructorNames = getCurrentConstructorNames cases in
@@ -481,7 +481,9 @@ module ExpandCatchAllForVariants = struct
         in
         match innerType with
         | Some ((Tvariant _ | Tpolyvariant _) as variant) ->
-          let currentConstructorNames = getCurrentConstructorNames cases in
+          let currentConstructorNames =
+            getCurrentConstructorNames ~mode:`option cases
+          in
           let hasNoneCase =
             cases
             |> List.exists (fun (c : Parsetree.case) ->