2 Add `"SideEffectFree"` and `"MaxNonPatternProduct"` options.
source | link

When _Alternatives are encountered, we look for first alternative containing _Condition or, _PatternTest, or _Pattern, matching this alternative can have side effects (binding variable to a name, done by Pattern, is here also considered a side effect). We leave all alternatives behind it alone, since extracting non-patterns from them could change behavior: f[patt /; sideEffect[] | a] := result is not equivalent to f[a] := result; f[patt /; sideEffect[]] := result. From alternatives before first with possible side-effect we extract non-pattern expressions.

BeginPackage@"DecomposePattern`"; Unprotect@"`*"; ClearAll@"`*"

DecomposePattern::usage = "DecomposePattern[patt] \
returns {{expr1, expr2, ...}, newPatt} expression, \
where expri are pattern-free expressions matched by patt, \
created by replacing Alternatives, present in patt, \
with their consecutive pattern-free elements. \
newPatt is pattern patt with pattern-free elements of Alternatives \
removed if possible. If expri are only expressions matched by patt, \
then newPatt is Alternatives[].";

Begin@"`Private`"; ClearAll@"`*"

patternPatt$patternPatt = _Blank | _BlankNullSequence | _BlankSequence | _Condition |
    _Except | _IgnoringInactive | _KeyValuePattern | _Optional |
    _OptionsPattern | _OrderlessPatternSequence |_Pattern | _PatternSequence |
    _PatternTest | _Repeated | _RepeatedNull;

$sideEffectPatt = _Condition | _PatternTest | _Pattern;

hold // Attributes = HoldAllComplete;

parse[alt_Alternatives] := Module[{held, lastBeforeSidEffPos, parsed},
    held = hold @@ Unevaluated@alt;
    (* Since patterns in alternatives are tested in order,
       and in ...Values non-patterns are always tried before patterns,
       then f[patt /; sideEffect[] | a] := ... is not equivalent to
       f[a] := ...; f[patt /; sideEffect[]] := ...,
       so we don't extract non-patterns that are in alternatives after
       first pattern with possible side effects. *)
    lastBeforeSidEffPos = Replace[
        Position[held,
            patt_ /; Not@FreeQ[Unevaluated@patt, _Condition$sideEffectPatt],
            {1}, 1, Heads -> False
        ],
        {{} :> Length@held, {{i_}} :> i - 1}
    ];
    parsed = Alternatives @@ parse /@ Take[held, lastBeforeSidEffPos];
    {
        Union @@ parsed[[All, 1]],
        Replace[
            Flatten[
                parsed[[All, 2]] |
                Alternatives @@ hold /@ Drop[held, lastBeforeSidEffPos]
            ],
            Verbatim[Alternatives]@patt_ :> patt
        ]
    }
]
parse[patt : $patternPatt] |:= _PatternTest]{{}, hold@patt}
parse[
    (expr : (h : Except[HoldPattern@Symbol@___, _Symbol] /;
   {1}, 1    MemberQ[Attributes@h, HeadsFlat ->| False
OneIdentity])[___]) |
    (expr_ /; FreeQ[Unevaluated@expr, _Alternatives])
] :=
    If[FreeQ[Unevaluated@expr, $patternPatt],
        {{hold@expr}, Alternatives[]}
    (* else *),
        {{}, hold@expr}
    ]
parse[expr : h_[args___]] := 
    With[{parsed = parse /@ Unevaluated@{h, args}},
        If[TrueQ[Times @@ (Length[#1]&) @@@ parsed <= $maxNonPattProduct],
        {{} :> Length@held, { {i_}} 
 :> i - 1}
    ];
    parsed = Alternatives @@ parse#1[##2] /@& Take[held@@@ Tuples@parsed[[All, lastBeforeSidEffPos];1]]
    {
        Union @@ parsed[[All, 1]] ,
        Replace[
        Replace[
    Flatten[
                parsed[[AllPosition[parsed, 
 2]] |
                Alternatives @@ hold /@ Drop[held  {_, lastBeforeSidEffPos]Except@Verbatim@Alternatives[]},
            ],
            Verbatim[Alternatives]@patt_{1}, :2, Heads -> pattFalse
        ]
    }
]
parse[patt : patternPatt] := {{}, hold@patt}
parse[
   ],
 (expr : (h : Except[HoldPattern@Symbol@___, _Symbol] /;
        MemberQ[Attributes@h, Flat | OneIdentity])[___]) | {
    (expr_ /; FreeQ[Unevaluated@expr, _Alternatives])
] :=
    If[FreeQ[Unevaluated@expr, patternPatt],
        {   {hold@expr}, :> Alternatives[]},
    (* else *),
        {{}, hold@expr}
    ]
parse[expr : h_[args___]] := 
  (* If there's With[{parsedonly =one parsepart /@with Unevaluated@{h,pattern args}},we {can
        #1[##2] & @@@ Tuples@parsed[[All, 1]],
        Replace[
       replace this part with pattern-only Position[parsed,version. *)
                        {_{pos_}} :> ReplacePart[hold@expr, Except@Verbatim@Alternatives[]}{1,
  pos - 1} ->
            {1}, 2, Heads -> False
            ]RuleCondition@parsed[[pos, 2]]],
            {
            _ :> hold@expr
  {} :> Alternatives[],
                (*}
 If there's only one part with pattern we can
       ]
            replace}
 this part with pattern-only version. *)
  (* else *),
            {{pos_}} :> ReplacePart[hold@expr, {1,hold@expr}
 pos - 1} ->
    ]
    ]
parse // Attributes = HoldAllComplete;

DecomposePattern // Options = {
    "SideEffectFree" RuleCondition@parsed[[pos,-> 2]]]None,
    "MaxNonPatternProduct" -> 10^6
};
DecomposePattern[patt_, OptionsPattern[]] :=
    Block[
    _ :> hold@expr
  {
          }
  $sideEffectPatt = Replace[OptionValue@"SideEffectFree",
                {None -> $sideEffectPatt, x_ :> Except[x, $sideEffectPatt]}
            ],
            $maxNonPattProduct = OptionValue@"MaxNonPatternProduct"
    ]
    }],
parse // Attributes = HoldAllComplete;

DecomposePattern@patt_ :=
    DeleteCases[parse@patt, hold, Infinity, Heads -> True]
    ]

End[]; Protect@"`*"; EndPackage[];
(* Assign some values to test possible evaluation leaks. *)
c := (Print@"evaluation leaked: c"; 0)
f := (Print@"evaluation leaked: f"; 1)
a | __h | b | HoldPattern[c | f[a | _g | b, f[c | d]]] | HoldPattern[f][e] | _?f | g // DecomposePattern
(* {
    {
        a, b, HoldPattern[f][e], HoldPattern[c],
        HoldPattern[f[a, f[c]]], HoldPattern[f[a, f[d]]],
        HoldPattern[f[b, f[c]]], HoldPattern[f[b, f[d]]]
    },
    __h | HoldPattern[f[_g, f[c | d]]] | _?f | g
} *)
ClearAll[c, f]

If we are sure that certain pattern, that potentially could have side effects, is free of them, we can set "SideEffectFree" option:

DecomposePattern[a | _?f | b | _?g | c]
DecomposePattern[a | _?f | b | _?g | c, "SideEffectFree" -> Verbatim[PatternTest][_, f]]
DecomposePattern[a | _?f | b | _?g | c, "SideEffectFree" -> Verbatim[PatternTest][_, f | g]]
(* {{a}, _?f | b | _?g | c} *)
(* {{a, b}, _?f | _?g | c} *)
(* {{a, b, c}, _?f | _?g} *)

When _Alternatives are encountered, we look for first alternative containing _Condition or _PatternTest, matching this alternative can have side effects. We leave all alternatives behind it alone, since extracting non-patterns from them could change behavior: f[patt /; sideEffect[] | a] := result is not equivalent to f[a] := result; f[patt /; sideEffect[]] := result. From alternatives before first with possible side-effect we extract non-pattern expressions.

BeginPackage@"DecomposePattern`"; Unprotect@"`*"; ClearAll@"`*"

DecomposePattern::usage = "DecomposePattern[patt] \
returns {{expr1, expr2, ...}, newPatt} expression, \
where expri are pattern-free expressions matched by patt, \
created by replacing Alternatives, present in patt, \
with their consecutive pattern-free elements. \
newPatt is pattern patt with pattern-free elements of Alternatives \
removed if possible. If expri are only expressions matched by patt, \
then newPatt is Alternatives[].";

Begin@"`Private`"; ClearAll@"`*"

patternPatt = _Blank | _BlankNullSequence | _BlankSequence | _Condition |
    _Except | _IgnoringInactive | _KeyValuePattern | _Optional |
    _OptionsPattern | _OrderlessPatternSequence |_Pattern | _PatternSequence |
    _PatternTest | _Repeated | _RepeatedNull;

hold // Attributes = HoldAllComplete;

parse[alt_Alternatives] := Module[{held, lastBeforeSidEffPos, parsed},
    held = hold @@ Unevaluated@alt;
    (* Since patterns in alternatives are tested in order,
       and in ...Values non-patterns are always tried before patterns,
       then f[patt /; sideEffect[] | a] := ... is not equivalent to
       f[a] := ...; f[patt /; sideEffect[]] := ...,
       so we don't extract non-patterns that are in alternatives after
       first pattern with possible side effects. *)
    lastBeforeSidEffPos = Replace[
        Position[held,
            patt_ /; Not@FreeQ[Unevaluated@patt, _Condition | _PatternTest],
            {1}, 1, Heads -> False
        ],
        {{} :> Length@held, {{i_}} :> i - 1}
    ];
    parsed = Alternatives @@ parse /@ Take[held, lastBeforeSidEffPos];
    {
        Union @@ parsed[[All, 1]],
        Replace[
            Flatten[
                parsed[[All, 2]] |
                Alternatives @@ hold /@ Drop[held, lastBeforeSidEffPos]
            ],
            Verbatim[Alternatives]@patt_ :> patt
        ]
    }
]
parse[patt : patternPatt] := {{}, hold@patt}
parse[
    (expr : (h : Except[HoldPattern@Symbol@___, _Symbol] /;
        MemberQ[Attributes@h, Flat | OneIdentity])[___]) |
    (expr_ /; FreeQ[Unevaluated@expr, _Alternatives])
] :=
    If[FreeQ[Unevaluated@expr, patternPatt],
        {{hold@expr}, Alternatives[]}
    (* else *),
        {{}, hold@expr}
    ]
parse[expr : h_[args___]] := 
     With[{parsed = parse /@ Unevaluated@{h, args}}, {
        #1[##2] & @@@ Tuples@parsed[[All, 1]],
        Replace[
            Position[parsed,
                {_, Except@Verbatim@Alternatives[]},
                 {1}, 2, Heads -> False
            ],
            {
                {} :> Alternatives[],
                (* If there's only one part with pattern we can
                   replace this part with pattern-only version. *)
                {{pos_}} :> ReplacePart[hold@expr, {1, pos - 1} ->
                    RuleCondition@parsed[[pos, 2]]],
                _ :> hold@expr
            }
        ]
    }]
parse // Attributes = HoldAllComplete;

DecomposePattern@patt_ :=
    DeleteCases[parse@patt, hold, Infinity, Heads -> True]

End[]; Protect@"`*"; EndPackage[];
(* Assign some values to test possible evaluation leaks. *)
c := (Print@"evaluation leaked: c"; 0)
f := (Print@"evaluation leaked: f"; 1)
a | __h | b | HoldPattern[c | f[a | _g | b, f[c | d]]] | HoldPattern[f][e] | _?f | g // DecomposePattern
(* {
    {
        a, b, HoldPattern[f][e], HoldPattern[c],
        HoldPattern[f[a, f[c]]], HoldPattern[f[a, f[d]]],
        HoldPattern[f[b, f[c]]], HoldPattern[f[b, f[d]]]
    },
    __h | HoldPattern[f[_g, f[c | d]]] | _?f | g
} *)
ClearAll[c, f]

When _Alternatives are encountered, we look for first alternative containing _Condition, _PatternTest, or _Pattern, matching this alternative can have side effects (binding variable to a name, done by Pattern, is here also considered a side effect). We leave all alternatives behind it alone, since extracting non-patterns from them could change behavior: f[patt /; sideEffect[] | a] := result is not equivalent to f[a] := result; f[patt /; sideEffect[]] := result. From alternatives before first with possible side-effect we extract non-pattern expressions.

BeginPackage@"DecomposePattern`"; Unprotect@"`*"; ClearAll@"`*"

DecomposePattern::usage = "DecomposePattern[patt] \
returns {{expr1, expr2, ...}, newPatt} expression, \
where expri are pattern-free expressions matched by patt, \
created by replacing Alternatives, present in patt, \
with their consecutive pattern-free elements. \
newPatt is pattern patt with pattern-free elements of Alternatives \
removed if possible. If expri are only expressions matched by patt, \
then newPatt is Alternatives[].";

Begin@"`Private`"; ClearAll@"`*"

$patternPatt = _Blank | _BlankNullSequence | _BlankSequence | _Condition |
    _Except | _IgnoringInactive | _KeyValuePattern | _Optional |
    _OptionsPattern | _OrderlessPatternSequence |_Pattern | _PatternSequence |
    _PatternTest | _Repeated | _RepeatedNull;

$sideEffectPatt = _Condition | _PatternTest | _Pattern;

hold // Attributes = HoldAllComplete;

parse[alt_Alternatives] := Module[{held, lastBeforeSidEffPos, parsed},
    held = hold @@ Unevaluated@alt;
    (* Since patterns in alternatives are tested in order,
       and in ...Values non-patterns are always tried before patterns,
       then f[patt /; sideEffect[] | a] := ... is not equivalent to
       f[a] := ...; f[patt /; sideEffect[]] := ...,
       so we don't extract non-patterns that are in alternatives after
       first pattern with possible side effects. *)
    lastBeforeSidEffPos = Replace[
        Position[held,
            patt_ /; Not@FreeQ[Unevaluated@patt, $sideEffectPatt],
            {1}, 1, Heads -> False
        ],
        {{} :> Length@held, {{i_}} :> i - 1}
    ];
    parsed = Alternatives @@ parse /@ Take[held, lastBeforeSidEffPos];
    {
        Union @@ parsed[[All, 1]],
        Replace[
            Flatten[
                parsed[[All, 2]] |
                Alternatives @@ hold /@ Drop[held, lastBeforeSidEffPos]
            ],
            Verbatim[Alternatives]@patt_ :> patt
        ]
    }
]
parse[patt : $patternPatt] := {{}, hold@patt}
parse[
    (expr : (h : Except[HoldPattern@Symbol@___, _Symbol] /;
        MemberQ[Attributes@h, Flat | OneIdentity])[___]) |
    (expr_ /; FreeQ[Unevaluated@expr, _Alternatives])
] :=
    If[FreeQ[Unevaluated@expr, $patternPatt],
        {{hold@expr}, Alternatives[]}
    (* else *),
        {{}, hold@expr}
    ]
parse[expr : h_[args___]] := 
    With[{parsed = parse /@ Unevaluated@{h, args}},
        If[TrueQ[Times @@ (Length[#1]&) @@@ parsed <= $maxNonPattProduct],
            { 
                #1[##2] & @@@ Tuples@parsed[[All, 1]]
                ,
                Replace[
                    Position[parsed, 
                        {_, Except@Verbatim@Alternatives[]},
                        {1}, 2, Heads -> False
                    ],
                    {
                        {} :> Alternatives[],
                        (* If there's only one part with pattern we can
                           replace this part with pattern-only version. *)
                        {{pos_}} :> ReplacePart[hold@expr, {1, pos - 1} ->
                            RuleCondition@parsed[[pos, 2]]],
                        _ :> hold@expr
                    }
                ]
            }
        (* else *),
            {{}, hold@expr}
        ]
    ]
parse // Attributes = HoldAllComplete;

DecomposePattern // Options = {
    "SideEffectFree" -> None,
    "MaxNonPatternProduct" -> 10^6
};
DecomposePattern[patt_, OptionsPattern[]] :=
    Block[
        {
            $sideEffectPatt = Replace[OptionValue@"SideEffectFree",
                {None -> $sideEffectPatt, x_ :> Except[x, $sideEffectPatt]}
            ],
            $maxNonPattProduct = OptionValue@"MaxNonPatternProduct"
        },
        DeleteCases[parse@patt, hold, Infinity, Heads -> True]
    ]

End[]; Protect@"`*"; EndPackage[];
(* Assign some values to test possible evaluation leaks. *)
c := (Print@"evaluation leaked: c"; 0)
f := (Print@"evaluation leaked: f"; 1)
a | __h | b | HoldPattern[c | f[a | _g | b, f[c | d]]] | HoldPattern[f][e] | _?f | g // DecomposePattern
(* {
    {
        a, b, HoldPattern[f][e], HoldPattern[c],
        HoldPattern[f[a, f[c]]], HoldPattern[f[a, f[d]]],
        HoldPattern[f[b, f[c]]], HoldPattern[f[b, f[d]]]
    },
    __h | HoldPattern[f[_g, f[c | d]]] | _?f | g
} *)
ClearAll[c, f]

If we are sure that certain pattern, that potentially could have side effects, is free of them, we can set "SideEffectFree" option:

DecomposePattern[a | _?f | b | _?g | c]
DecomposePattern[a | _?f | b | _?g | c, "SideEffectFree" -> Verbatim[PatternTest][_, f]]
DecomposePattern[a | _?f | b | _?g | c, "SideEffectFree" -> Verbatim[PatternTest][_, f | g]]
(* {{a}, _?f | b | _?g | c} *)
(* {{a, b}, _?f | _?g | c} *)
(* {{a, b, c}, _?f | _?g} *)
1
source | link

We can create a recursive top-down parser that, for each sub-expression, of pattern, will call itself on parts of this sub-expression, assemble results, and return pair containing, as first element, list of "non-pattern" expressions and, as second element, "real pattern", that in DownValues will be equivalent to given pattern sub-expression.

Recursion stops on expressions treated as "real patterns" by DownValues and on expressions free of _Alternatives.

When _Alternatives are encountered, we look for first alternative containing _Condition or _PatternTest, matching this alternative can have side effects. We leave all alternatives behind it alone, since extracting non-patterns from them could change behavior: f[patt /; sideEffect[] | a] := result is not equivalent to f[a] := result; f[patt /; sideEffect[]] := result. From alternatives before first with possible side-effect we extract non-pattern expressions.

Parser, with behavior sketched above, is implemented in following package.

BeginPackage@"DecomposePattern`"; Unprotect@"`*"; ClearAll@"`*"

DecomposePattern::usage = "DecomposePattern[patt] \
returns {{expr1, expr2, ...}, newPatt} expression, \
where expri are pattern-free expressions matched by patt, \
created by replacing Alternatives, present in patt, \
with their consecutive pattern-free elements. \
newPatt is pattern patt with pattern-free elements of Alternatives \
removed if possible. If expri are only expressions matched by patt, \
then newPatt is Alternatives[].";

Begin@"`Private`"; ClearAll@"`*"

patternPatt = _Blank | _BlankNullSequence | _BlankSequence | _Condition |
    _Except | _IgnoringInactive | _KeyValuePattern | _Optional |
    _OptionsPattern | _OrderlessPatternSequence |_Pattern | _PatternSequence |
    _PatternTest | _Repeated | _RepeatedNull;

hold // Attributes = HoldAllComplete;

parse[alt_Alternatives] := Module[{held, lastBeforeSidEffPos, parsed},
    held = hold @@ Unevaluated@alt;
    (* Since patterns in alternatives are tested in order,
       and in ...Values non-patterns are always tried before patterns,
       then f[patt /; sideEffect[] | a] := ... is not equivalent to
       f[a] := ...; f[patt /; sideEffect[]] := ...,
       so we don't extract non-patterns that are in alternatives after
       first pattern with possible side effects. *)
    lastBeforeSidEffPos = Replace[
        Position[held,
            patt_ /; Not@FreeQ[Unevaluated@patt, _Condition | _PatternTest],
            {1}, 1, Heads -> False
        ],
        {{} :> Length@held, {{i_}} :> i - 1}
    ];
    parsed = Alternatives @@ parse /@ Take[held, lastBeforeSidEffPos];
    {
        Union @@ parsed[[All, 1]],
        Replace[
            Flatten[
                parsed[[All, 2]] |
                Alternatives @@ hold /@ Drop[held, lastBeforeSidEffPos]
            ],
            Verbatim[Alternatives]@patt_ :> patt
        ]
    }
]
parse[patt : patternPatt] := {{}, hold@patt}
parse[
    (expr : (h : Except[HoldPattern@Symbol@___, _Symbol] /;
        MemberQ[Attributes@h, Flat | OneIdentity])[___]) |
    (expr_ /; FreeQ[Unevaluated@expr, _Alternatives])
] :=
    If[FreeQ[Unevaluated@expr, patternPatt],
        {{hold@expr}, Alternatives[]}
    (* else *),
        {{}, hold@expr}
    ]
parse[expr : h_[args___]] := 
    With[{parsed = parse /@ Unevaluated@{h, args}}, {
        #1[##2] & @@@ Tuples@parsed[[All, 1]],
        Replace[
            Position[parsed,
                {_, Except@Verbatim@Alternatives[]},
                {1}, 2, Heads -> False
            ],
            {
                {} :> Alternatives[],
                (* If there's only one part with pattern we can
                   replace this part with pattern-only version. *)
                {{pos_}} :> ReplacePart[hold@expr, {1, pos - 1} ->
                    RuleCondition@parsed[[pos, 2]]],
                _ :> hold@expr
            }
        ]
    }]
parse // Attributes = HoldAllComplete;

DecomposePattern@patt_ :=
    DeleteCases[parse@patt, hold, Infinity, Heads -> True]

End[]; Protect@"`*"; EndPackage[];

Simple usage examples:

ClearAll[a, b, c, d, e, f, g, h]

f[g[a | b | c | _h], x] // DecomposePattern
(* {{f[g[a], x], f[g[b], x], f[g[c], x]}, f[g[_h], x]} *)

f[g[a | b], _h] // DecomposePattern
(* {{}, f[g[a | b], _h]} *)

f[a | b | c, d | e] // DecomposePattern
(* {{f[a, d], f[a, e], f[b, d], f[b, e], f[c, d], f[c, e]}, Alternatives[]} *)

f[a | b | c, d | _h | e] // DecomposePattern
(* {{f[a, d], f[a, e], f[b, d], f[b, e], f[c, d], f[c, e]}, f[a | b | c, _h]} *)

Complex example with held patterns:

(* Assign some values to test possible evaluation leaks. *)
c := (Print@"evaluation leaked: c"; 0)
f := (Print@"evaluation leaked: f"; 1)
a | __h | b | HoldPattern[c | f[a | _g | b, f[c | d]]] | HoldPattern[f][e] | _?f | g // DecomposePattern
(* {
    {
        a, b, HoldPattern[f][e], HoldPattern[c],
        HoldPattern[f[a, f[c]]], HoldPattern[f[a, f[d]]],
        HoldPattern[f[b, f[c]]], HoldPattern[f[b, f[d]]]
    },
    __h | HoldPattern[f[_g, f[c | d]]] | _?f | g
} *)
ClearAll[c, f]