One thing worth noting is that the “Conjunctive grammars” exist, as a parsimonious extension of CFG with an intersection operator &. They retain all formal properties of general CFGs (while adding closure under intersection), including the O(|G|n³) worst-case bound.
There are also the “Boolean grammars”, which add negation (and similarly preserve everything, including performance). Alexander Okhotin has written a great deal on the subject of both.
Using such a formalism, the if <expr> { case would be quite simple - intersect the <expr> with the permitted forms (in the conjunctive case), or the negation of the forbidden forms (in the boolean case).
While this slightly erodes the ability to generate a classic EBNF, it doesn’t do so by all that much - in particular, an EBNF that simply strips these things would produce a parse forest that is a strict superset of that produced by a full conjunctive or boolean parser, aside from the additional parse trees from the right hand side of the conjunction (which in this case, and I suspect in general for Rust, will be mere duplicates of the left hand side).
EDIT: Also relevant re: precedence are the Floyd languages, sometimes called the operator-precedence languages. This proper subset of CFG is classically parsed by either the Shunting Yard algorithm (LR-like) or Pratt parsing (LL-like). It makes use of an operator precedence matrix, which denotes a partially ordered set over the operators. See also, this dissertation.
Such a matrix can easily be defined by giving pairwise ‘tighter’, ‘looser’, and ‘eqv’ annotations (and a unary associativity annotation) on rule clauses of the form (NT opT NT). (Strictly speaking, this only defines the upper triangular matrix, and leaves the lower triangular matrix to be defined by inversion - however, making + tighter than * on the left, and looser on the right, is a bit of a… specialized need.)