@@ -752,6 +752,99 @@ void match_statement_guarded_wildcard_is_refutable() {
752752 assertThat (guardedWildcard .falseSuccessor ()).isEqualTo (barBlock );
753753 }
754754
755+ @ Test
756+ void match_statement_group_pattern_is_irrefutable () {
757+ ControlFlowGraph cfg = cfg ("""
758+ foo() """ ,
759+ "match x:" ,
760+ " case \" a\" : y()" ,
761+ " case (z): w()" ,
762+ "bar()"
763+ );
764+
765+ PythonCfgBranchingBlock start = (PythonCfgBranchingBlock ) cfg .start ();
766+ PythonCfgEndBlock end = (PythonCfgEndBlock ) cfg .end ();
767+ assertThat (end .predecessors ()).hasSize (1 );
768+ CfgBlock barBlock = end .predecessors ().stream ().findFirst ().get ();
769+
770+ // case "a" false successor must be the irrefutable block (case (z)), not barBlock
771+ PythonCfgSimpleBlock irrefutableBlock = (PythonCfgSimpleBlock ) start .falseSuccessor ();
772+ assertThat (irrefutableBlock .elements ()).extracting (Tree ::getKind )
773+ .containsExactly (Kind .NAME , Kind .GROUP_PATTERN );
774+
775+ CfgBlock irrefutableBody = irrefutableBlock .successors ().stream ().findFirst ().get ();
776+ assertThat (irrefutableBody .successors ()).containsExactly (barBlock );
777+ }
778+
779+ @ Test
780+ void match_statement_group_wildcard_pattern_is_irrefutable () {
781+ ControlFlowGraph cfg = cfg ("""
782+ foo() """ ,
783+ "match x:" ,
784+ " case \" a\" : y()" ,
785+ " case (_): w()" ,
786+ "bar()"
787+ );
788+
789+ PythonCfgBranchingBlock start = (PythonCfgBranchingBlock ) cfg .start ();
790+ // case "a" false successor must be a simple block (irrefutable), not a branching block
791+ PythonCfgSimpleBlock irrefutableBlock = (PythonCfgSimpleBlock ) start .falseSuccessor ();
792+ assertThat (irrefutableBlock .elements ()).extracting (Tree ::getKind )
793+ .containsExactly (Kind .NAME , Kind .GROUP_PATTERN );
794+ }
795+
796+ @ Test
797+ void match_statement_nested_group_pattern_is_irrefutable () {
798+ ControlFlowGraph cfg = cfg ("""
799+ foo() """ ,
800+ "match x:" ,
801+ " case \" a\" : y()" ,
802+ " case ((z)): w()" ,
803+ "bar()"
804+ );
805+
806+ PythonCfgBranchingBlock start = (PythonCfgBranchingBlock ) cfg .start ();
807+ // case "a" false successor must be a simple block (irrefutable), not a branching block
808+ PythonCfgSimpleBlock irrefutableBlock = (PythonCfgSimpleBlock ) start .falseSuccessor ();
809+ assertThat (irrefutableBlock .elements ()).extracting (Tree ::getKind )
810+ .containsExactly (Kind .NAME , Kind .GROUP_PATTERN );
811+ }
812+
813+ @ Test
814+ void match_statement_group_literal_pattern_is_refutable () {
815+ ControlFlowGraph cfg = cfg ("""
816+ foo() """ ,
817+ "match x:" ,
818+ " case \" a\" : y()" ,
819+ " case (42): w()" ,
820+ "bar()"
821+ );
822+
823+ // Both cases are refutable — the second case must be a branching block
824+ PythonCfgBranchingBlock start = (PythonCfgBranchingBlock ) cfg .start ();
825+ PythonCfgBranchingBlock secondCase = (PythonCfgBranchingBlock ) start .falseSuccessor ();
826+ assertThat (secondCase ).isInstanceOf (PythonCfgBranchingBlock .class );
827+ // The false path of the second case leads directly to barBlock (no-match path)
828+ PythonCfgEndBlock end = (PythonCfgEndBlock ) cfg .end ();
829+ CfgBlock barBlock = end .predecessors ().stream ().findFirst ().get ();
830+ assertThat (secondCase .falseSuccessor ()).isEqualTo (barBlock );
831+ }
832+
833+ @ Test
834+ void match_statement_guarded_group_pattern_is_refutable () {
835+ ControlFlowGraph cfg = cfg ("""
836+ foo() """ ,
837+ "match x:" ,
838+ " case \" a\" : y()" ,
839+ " case (z) if w(): v()" ,
840+ "bar()"
841+ );
842+
843+ // Both cases are refutable — the second case must be a branching block
844+ PythonCfgBranchingBlock start = (PythonCfgBranchingBlock ) cfg .start ();
845+ assertThat (start .falseSuccessor ()).isInstanceOf (PythonCfgBranchingBlock .class );
846+ }
847+
755848 @ Test
756849 void CFGBlock_toString () {
757850 PythonCfgEndBlock endBlock = new PythonCfgEndBlock ();
0 commit comments