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

  1. Delete CoalesceToTernaryRector::class from rector.php / the imported set and re-run vendor/bin/rector dry-run
  2. If a specific ?? must become a ternary, rewrite that site manually and double-check isset vs falsy semantics
  3. Keep ?? — it is the idiomatic modern form; there is no upgrade path requiring its removal
  4. 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

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


AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21). Data as JSON: /api/errors/66c93166d1ce1363. Report an issue: GitHub.