rectorphp/rector · error · ShouldNotHappenException
"%s" rule is deprecated, as risky. The "??" and "?:" operato
Error message
"%s" rule is deprecated, as risky. The "??" and "?:" operators are not interchangeable and a regression has to be fixed manually
What it means
CoalesceToTernaryRector used to rewrite $a ?? $b into an equivalent ternary with isset(). It is deprecated because ?? (null coalescing) and ?: (elvis) are NOT interchangeable — ?? checks isset/null while ?: casts to bool — so automated rewriting caused regressions that had to be fixed by hand. The rule's refactor() now throws ShouldNotHappenException on every Coalesce node it visits; the class remains only as a tombstone so stale configs fail loudly instead of rewriting code.
Source
Thrown at rules/CodeQuality/Rector/Coalesce/CoalesceToTernaryRector.php:48
{
return $a ?: 'foo';
}
CODE_SAMPLE
)]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Coalesce::class];
}
/**
* @param Coalesce $node
*/
public function refactor(Node $node): ?Node
{
throw new ShouldNotHappenException(sprintf('"%s" rule is deprecated, as risky. The "??" and "?:" operators are not interchangeable and a regression has to be fixed manually', self::class));
}
}
View on GitHub (pinned to 408fcb0ff1)
Solutions
- Delete CoalesceToTernaryRector::class from rector.php / the imported set and re-run vendor/bin/rector dry-run
- If a specific ?? must become a ternary, rewrite that site manually and double-check isset vs falsy semantics
- Keep ?? — it is the idiomatic modern form; there is no upgrade path requiring its removal
- Search all config files (grep -r CoalesceToTernaryRector) to catch set files that import it indirectly
Example fix
// before (rector.php)
->withRules([
\Rector\CodeQuality\Rector\Coalesce\CoalesceToTernaryRector::class,
])
// after
// rule removed; if truly needed, hand-convert:
// $name = $user->name ?? 'guest'; stays as is (preferred)
// $x = $a ?: $b; only if you really mean falsy-check, write it yourself Defensive patterns
Strategy: validation
Validate before calling
$deprecated = ['CoalesceToTernaryRector'];
foreach (glob(__DIR__ . '/rector*.php') as $config) {
$src = file_get_contents($config);
foreach ($deprecated as $rule) {
if (str_contains($src, $rule)) {
fwrite(STDERR, "Remove deprecated rule {$rule} from {$config}\n");
exit(1);
}
}
} Prevention
- Prune rector.php of deprecated rules before every Rector upgrade
- Never mass-rewrite ?? to ?: — their null-vs-falsy semantics differ
- Run dry-run in CI on a scratch branch so a guard throw never reaches main
- Keep shared rule sets in version control and update them centrally when rules are removed
When it happens
Trigger: Registering rules/CodeQuality/Rector/Coalesce/CoalesceToTernaryRector.php in the Rector config and running process/dry-run over any PHP file containing a ?? (Coalesce) expression. The throw happens at rules/CodeQuality/Rector/Coalesce/CoalesceToTernaryRector.php:48 on the first coalesce expression encountered.
Common situations: A rector.php or imported set carried over from an older Rector version still lists CoalesceToTernaryRector; after upgrading the dependency, the first dry-run on any file with ?? crashes with this exception. Teams that kept a 'code quality' rule whitelist in a shared package hit it across many repositories at once.
Related errors
- "%s" rule is deprecated, as turning a docblock type into a r
- "%s" is deprecated as depends on context and personal prefer
- "%s" rule is deprecated, as it is a personal preference that
- "%s" is deprecated, as simplifying regex ranges is a persona
- "%s" is deprecated as it worsens readability. Use "match (tr
AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21).
Data as JSON: /api/errors/66c93166d1ce1363.
Report an issue: GitHub.