Adding enhanced switch support to ThrowableNotThrown
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/bugs/ThrowableNotThrown.java b/java/java.hints/src/org/netbeans/modules/java/hints/bugs/ThrowableNotThrown.java
index da87859..d14747d 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/bugs/ThrowableNotThrown.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/bugs/ThrowableNotThrown.java
@@ -19,16 +19,22 @@
 package org.netbeans.modules.java.hints.bugs;
 
 import com.sun.source.tree.AssignmentTree;
+import com.sun.source.tree.BindingPatternTree;
+import com.sun.source.tree.CaseTree;
 import com.sun.source.tree.ConditionalExpressionTree;
 import com.sun.source.tree.ForLoopTree;
 import com.sun.source.tree.MethodInvocationTree;
 import com.sun.source.tree.NewClassTree;
+import com.sun.source.tree.PatternCaseLabelTree;
+import com.sun.source.tree.SwitchExpressionTree;
+import com.sun.source.tree.SwitchTree;
 import com.sun.source.tree.Tree;
+import com.sun.source.tree.Tree.Kind;
 import com.sun.source.tree.VariableTree;
 import com.sun.source.util.TreePath;
 import java.util.Collection;
-import java.util.EnumSet;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import javax.lang.model.element.Element;
@@ -90,13 +96,6 @@
         return null;
     }
     
-    private static final EnumSet<ElementKind> LOCAL_VARIABLES = EnumSet.of(
-            ElementKind.LOCAL_VARIABLE,
-            ElementKind.PARAMETER,
-            ElementKind.RESOURCE_VARIABLE,
-            ElementKind.EXCEPTION_PARAMETER
-    );
-    
     private static TreePath findEnclosingMethodPath(TreePath path) {
         TreePath enclosingMethodPath = path;
         TreePath nextPath = enclosingMethodPath.getParentPath();
@@ -303,7 +302,7 @@
                         Element el = info.getTrees().getElement(new TreePath(excPath, var));
                         if (el == null || el.getKind() == ElementKind.FIELD) {
                             return true;
-                        } else if (LOCAL_VARIABLES.contains(el.getKind())) {
+                        } else if (Flow.LOCAL_VARIABLES.contains(el.getKind())) {
                             varAssignments.add(as.getExpression());
                         }
                         process = true;
@@ -359,10 +358,40 @@
                         break;
                     }
 
+                    case SWITCH: {
+                        SwitchTree st = (SwitchTree) leaf;
+
+                        if (st.getExpression() == prevLeaf) {
+                            collectCaseBindings(st.getCases());
+                        }
+
+                        break;
+                    }
+                    case SWITCH_EXPRESSION: {
+                        SwitchExpressionTree st = (SwitchExpressionTree) leaf;
+
+                        if (st.getExpression() == prevLeaf) {
+                            collectCaseBindings(st.getCases());
+                            process = true;
+                        }
+                        break;
+                    }
                 }
                 prevLeaf = excPath.getLeaf();
             } while (process);
             return varAssignments.isEmpty() ? Boolean.FALSE : null;
         }
+
+        private void collectCaseBindings(List<? extends CaseTree> cases) {
+            //all binding patterns should be considered as new target variables for the selector value:
+            cases.stream()
+                 .flatMap(cse -> cse.getLabels().stream())
+                 .filter(label -> label.getKind() == Kind.PATTERN_CASE_LABEL)
+                 .map(label -> ((PatternCaseLabelTree) label).getPattern())
+                 .filter(pattern -> pattern.getKind() == Kind.BINDING_PATTERN)
+                 .map(pattern -> ((BindingPatternTree) pattern).getVariable())
+                 .filter(var -> !var.getName().isEmpty())
+                 .forEach(varAssignments::add);
+        }
     }
 }
diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/Flow.java b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/Flow.java
index 860bd8b..cb499aa 100644
--- a/java/java.hints/src/org/netbeans/modules/java/hints/introduce/Flow.java
+++ b/java/java.hints/src/org/netbeans/modules/java/hints/introduce/Flow.java
@@ -114,6 +114,26 @@
  */
 public class Flow {
 
+    public static final Set<ElementKind> LOCAL_VARIABLES =
+            Set.of(ElementKind.BINDING_VARIABLE,
+                   ElementKind.EXCEPTION_PARAMETER,
+                   ElementKind.LOCAL_VARIABLE,
+                   ElementKind.PARAMETER,
+                   ElementKind.RESOURCE_VARIABLE
+            );
+    private static final Set<ElementKind> IMPLICITLY_INITIALIZED_VARIABLES =
+            Set.of(ElementKind.BINDING_VARIABLE,
+                   ElementKind.EXCEPTION_PARAMETER,
+                   ElementKind.PARAMETER
+            );
+    private static final Set<ElementKind> SUPPORTED_VARIABLES;
+
+    static {
+        SUPPORTED_VARIABLES = EnumSet.noneOf(ElementKind.class);
+        SUPPORTED_VARIABLES.addAll(LOCAL_VARIABLES);
+        SUPPORTED_VARIABLES.add(ElementKind.FIELD);
+    }
+
     public static FlowResult assignmentsForUse(CompilationInfo info, AtomicBoolean cancel) {
         return assignmentsForUse(info, new AtomicBooleanCancel(cancel));
     }
@@ -382,7 +402,6 @@
         private Map<Tree, Collection<Map<Element, State>>> resumeBefore = new IdentityHashMap<Tree, Collection<Map<Element, State>>>();
         private Map<Tree, Collection<Map<Element, State>>> resumeAfter = new IdentityHashMap<Tree, Collection<Map<Element, State>>>();
         private Map<TypeMirror, Map<Element, State>> resumeOnExceptionHandler = new IdentityHashMap<TypeMirror, Map<Element, State>>();
-        private boolean inParameters;
         private Tree nearestMethod;
         private Set<Element> currentMethodVariables = Collections.newSetFromMap(new IdentityHashMap<Element, Boolean>());
         private final Set<Tree> deadBranches = new HashSet<Tree>();
@@ -645,17 +664,32 @@
             Element e = info.getTrees().getElement(getCurrentPath());
             
             if (e != null && SUPPORTED_VARIABLES.contains(e.getKind())) {
-                // note: if the variable does not have an initializer and is not a parameter (inParameters = true),
+                // note: if the variable does not have an initializer and is not an implicitly initialized variable,
                 // the State will receive null as an assignment to indicate an uninitializer
+                boolean implicitlyInitialized = IMPLICITLY_INITIALIZED_VARIABLES.contains(e.getKind());
                 TreePath tp = 
                             node.getInitializer() != null ? 
                                     new TreePath(getCurrentPath(), node.getInitializer()) : 
-                                    inParameters ? getCurrentPath() : null;
+                                    implicitlyInitialized ? getCurrentPath() : null;
                 recordVariableState(e, tp);
                 currentMethodVariables.add((Element) e);
-                TreePath pp = getCurrentPath().getParentPath();
-                if (pp != null) {
-                    addScopedVariable(pp.getLeaf(), e);
+                TreePath scopePath;
+                if (e.getKind() == ElementKind.BINDING_VARIABLE) {
+                    //a wider scope does not matter too much:
+                    scopePath = getCurrentPath().getParentPath();
+                    SCOPE_SEARCH: while (scopePath != null) {
+                        switch (scopePath.getLeaf().getKind()) {
+                            case VARIABLE, BLOCK: break SCOPE_SEARCH;
+                            default:
+                                scopePath = scopePath.getParentPath();
+                                continue SCOPE_SEARCH;
+                        }
+                    }
+                } else {
+                    scopePath = getCurrentPath().getParentPath();
+                }
+                if (scopePath != null) {
+                    addScopedVariable(scopePath.getLeaf(), e);
                 }
             }
             
@@ -921,15 +955,7 @@
                 scan(node.getModifiers(), null);
                 scan(node.getReturnType(), null);
                 scan(node.getTypeParameters(), null);
-
-                inParameters = true;
-
-                try {
-                    scan(node.getParameters(), null);
-                } finally {
-                    inParameters = false;
-                }
-
+                scan(node.getParameters(), null);
                 scan(node.getThrows(), null);
                 
                 List<Tree> additionalTrees = p != null ? p.initializers : Collections.<Tree>emptyList();
@@ -1393,13 +1419,8 @@
             }
             
             try {
-                inParameters = true;
+                scan(node.getParameters(), null);
 
-                try {
-                    scan(node.getParameters(), null);
-                } finally {
-                    inParameters = false;
-                }
                 b = scan(node.getBody(), p);
             
                 Set<Element> assigned = new HashSet<Element>();
@@ -1723,9 +1744,7 @@
         }
 
         public Boolean visitCatch(CatchTree node, ConstructorData p) {
-            inParameters = true;
             scan(node.getParameter(), p);
-            inParameters = false;
             scan(node.getBlock(), p);
             return null;
         }
@@ -1810,9 +1829,6 @@
         }
     }
     
-    private static final Set<ElementKind> SUPPORTED_VARIABLES = EnumSet.of(ElementKind.EXCEPTION_PARAMETER, ElementKind.LOCAL_VARIABLE, ElementKind.PARAMETER, ElementKind.FIELD);
-    private static final Set<ElementKind> LOCAL_VARIABLES = EnumSet.of(ElementKind.EXCEPTION_PARAMETER, ElementKind.LOCAL_VARIABLE, ElementKind.PARAMETER);
-    
     static class State {
         private final Set<TreePath> assignments;
         private final boolean reassigned;
diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/bugs/ThrowableNotThrownTest.java b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/bugs/ThrowableNotThrownTest.java
index 482c8bc..a7a6641 100644
--- a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/bugs/ThrowableNotThrownTest.java
+++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/bugs/ThrowableNotThrownTest.java
@@ -347,4 +347,130 @@
                 .run(ThrowableNotThrown.class)
                 .assertWarnings();
     }
+
+    public void testEnhancedSwitch1() throws Exception {
+        HintTest.create()
+                .sourceLevel(25)
+                .input("""
+                       package test;
+                       import java.io.FileInputStream;
+                       import java.io.IOException;
+
+                       public class Test {
+                           void test() throws Throwable {
+                               try {
+                                   new FileInputStream("/foo");
+                               } catch (IOException ex) {
+                                   switch (ex.getCause()) {
+                                       case IOException ioe -> throw ioe;
+                                       case Throwable _ -> throw ex;
+                                   }
+                               }
+                           }
+                       }
+                       """)
+                .run(ThrowableNotThrown.class)
+                .assertWarnings();
+    }
+
+    public void testEnhancedSwitch2() throws Exception {
+        HintTest.create()
+                .sourceLevel(25)
+                .input("""
+                       package test;
+                       import java.io.FileInputStream;
+                       import java.io.IOException;
+
+                       public class Test {
+                           void test() throws Throwable {
+                               try {
+                                   new FileInputStream("/foo");
+                               } catch (IOException ex) {
+                                   switch (ex.getCause()) {
+                                       case IOException ioe -> throw ex;
+                                       case Throwable _ -> throw ex;
+                                   }
+                               }
+                           }
+                       }
+                       """)
+                .run(ThrowableNotThrown.class)
+                .assertWarnings("9:20-9:33:verifier:Throwable method result is ignored");
+    }
+
+    public void testEnhancedSwitchExpressionNoWarn1() throws Exception {
+        HintTest.create()
+                .sourceLevel(25)
+                .input("""
+                       package test;
+                       import java.io.FileInputStream;
+                       import java.io.IOException;
+
+                       public class Test {
+                           void test() throws Throwable {
+                               try {
+                                   new FileInputStream("/foo");
+                               } catch (IOException ex) {
+                                   throw switch (ex.getCause()) {
+                                       case IOException ioe -> ioe;
+                                       case Throwable _ -> ex;
+                                   };
+                               }
+                           }
+                       }
+                       """)
+                .run(ThrowableNotThrown.class)
+                .assertWarnings();
+    }
+
+    public void testEnhancedSwitchExpressionNoWarn2() throws Exception {
+        HintTest.create()
+                .sourceLevel(25)
+                .input("""
+                       package test;
+                       import java.io.FileInputStream;
+                       import java.io.IOException;
+
+                       public class Test {
+                           void test() throws Throwable {
+                               try {
+                                   new FileInputStream("/foo");
+                               } catch (IOException ex) {
+                                   Throwable t = switch (ex.getCause()) {
+                                       case IOException ioe -> ioe;
+                                       case Throwable tt -> throw tt;
+                                   };
+                               }
+                           }
+                       }
+                       """)
+                .run(ThrowableNotThrown.class)
+                .assertWarnings();
+    }
+
+    public void testEnhancedSwitchExpressionWarn() throws Exception {
+        HintTest.create()
+                .sourceLevel(25)
+                .input("""
+                       package test;
+                       import java.io.FileInputStream;
+                       import java.io.IOException;
+
+                       public class Test {
+                           void test() throws Throwable {
+                               try {
+                                   new FileInputStream("/foo");
+                               } catch (IOException ex) {
+                                   Throwable t = switch (ex.getCause()) {
+                                       case IOException ioe -> ioe;
+                                       case Throwable tt -> tt;
+                                   };
+                               }
+                           }
+                       }
+                       """)
+                .run(ThrowableNotThrown.class)
+                .assertWarnings("9:34-9:47:verifier:Throwable method result is ignored");
+    }
+
 }
diff --git a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/introduce/FlowTest.java b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/introduce/FlowTest.java
index 14973c5..50535c7 100644
--- a/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/introduce/FlowTest.java
+++ b/java/java.hints/test/unit/src/org/netbeans/modules/java/hints/introduce/FlowTest.java
@@ -1597,6 +1597,61 @@
                     "4");
     }
 
+    public void testParameters() throws Exception {
+        performTest("""
+                    package test;
+                    public class Test {
+                        static void t(int ii) {
+                            System.err.println(i`i);
+                        }
+                    }
+                    """,
+                    "int ii");
+    }
+
+    public void testLambdaParameters() throws Exception {
+        performTest("""
+                    package test;
+                    public class Test {
+                        static void t() {
+                            I i = ii -> System.err.println(i`i);
+                        }
+                        interface I {
+                            public void consume(int ii);
+                        }
+                    }
+                    """,
+                    "int ii");
+    }
+
+    public void testCatchParameters() throws Exception {
+        performTest("""
+                    package test;
+                    public class Test {
+                        static void t() {
+                            try {
+                            } catch (Throwable tt) {
+                                System.err.println(t`t);
+                            }
+                        }
+                    }
+                    """,
+                    "Throwable tt");
+    }
+
+    public void testBindingVariables() throws Exception {
+        sourceLevel = "21";
+        performTest("""
+                    package test;
+                    public class Test {
+                        static void t(Object o) {
+                            boolean b = o instanceof Integer ii && i`i == 0;
+                        }
+                    }
+                    """,
+                    "Integer ii");
+    }
+
     private void performFinalCandidatesTest(String code, boolean allowErrors, String... finalCandidates) throws Exception {
         prepareTest(code, allowErrors);