Skip to content

Treat the | in a catch type list as a union type separator - #94

Closed
XRafik wants to merge 1 commit into
php-collective:masterfrom
XRafik:catch-type-separator-spacing
Closed

XRafik wants to merge 1 commit into
php-collective:masterfrom
XRafik:catch-type-separator-spacing

Conversation

@XRafik

@XRafik XRafik commented Oct 25, 2026

Copy link
Copy Markdown

Closes #88.

Problem

A multi-catch type list is written without spaces, exactly like a union type:

} catch (ValueError | TypeError $e) {   // passes
} catch (ValueError|TypeError $e) {     // PSR12.Operators.OperatorSpacing.NoSpaceBefore + NoSpaceAfter

PHP tokenizes the catch list with plain T_BITWISE_OR tokens (not T_TYPE_UNION), so Universal.Operators.TypeSeparatorSpacing never sees it while the inherited PSR12.Operators.OperatorSpacing reads it as a bitwise operator and demands a space on both sides - the opposite of the type separator rule. Downstream projects such as cakephp/cakephp#19740 had to silence both PSR12 codes in their phpcs.xml, and that also silenced the real bitwise operators.

Fix

A new PhpCollective.Operators.OperatorSpacing extends the PSR12 sniff. It keeps every inherited check and treats the catch-list | like a union type separator: no space, fixable. The | inside the catch parenthesis is recognized via the innermost enclosing parenthesis being owned by the catch keyword, so a real bitwise operator in the catch body (and a DNF & inside the catch list) keeps the inherited spacing behavior.

The ruleset swaps in the subclass and excludes the vendor PSR12.Operators.OperatorSpacing.

Verification

  • catch (ValueError|TypeError $e) -> no operator errors; catch (ValueError | TypeError $e) -> 2 fixable PhpCollective.Operators.OperatorSpacing.SpaceBefore/SpaceAfter errors, and phpcbf strips the spaces
  • a real bitwise $a|$b in the catch body still reports the inherited NoSpaceBefore/NoSpaceAfter
  • DNF in catch: the outer | gets the type separator rule, the inner & keeps the inherited rule
  • unit tests: the sniff reports 3 fixable errors and the fixer produces the expected after file (both pass)
  • PHPStan level 8 and the repo cs-check are clean on the changed files

PHP tokenizes a catch type list (catch (ValueError|TypeError $e)) with plain T_BITWISE_OR tokens, so PSR12.Operators.OperatorSpacing read it as a bitwise operator and demanded a space on both sides - the opposite of the type separator rule from Universal.Operators.TypeSeparatorSpacing, which only listens for T_TYPE_UNION and never sees a catch list. Downstream projects such as cakephp/cakephp had to silence both NoSpaceBefore and NoSpaceAfter in their phpcs.xml, which also silenced the real bitwise operators.

The new PhpCollective.Operators.OperatorSpacing extends the PSR12 sniff, keeps every inherited check, and re-rules the catch-list | like a union type separator: no space, fixable. The ruleset swaps in the subclass and excludes the vendor sniff. Closes php-collective#88.
@XRafik

XRafik commented Oct 25, 2026

Copy link
Copy Markdown
Author

Closing in favor of #92 — you landed the same fix (repo-owned OperatorSpacing subclass + CatchTypeSeparatorSpacing sniff) while this one was in flight, so one diff surface is better than two. Thanks for the fast turnaround on #88.

@XRafik XRafik closed this Oct 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multi-catch pipe: PSR12 OperatorSpacing requires spaces, union types forbid them

1 participant