Skip to content

ResponseStatusCodeRector crashes on first-class callable of a method named assert* #9954

Closed
rectorphp/rector-symfony
#1103

Description

@devnix

Bug Report

Subject Details
Rector version Rector 2.8.3 (also 2.5.7)

ResponseStatusCodeRector crashes when it visits a first-class callable whose method name starts with assert, for example $validator->assert(...). This is a common pattern with validation libraries such as PSL (Psl\Type\string()->assert(...)).

processMethodCall() sends every method call whose name starts with assert to processAssertMethodCall():

https://github.com/rectorphp/rector-symfony/blob/5e4e27ac2f9beb1ecdb268dddf5e7e5a3954cc0d/rules/CodeQuality/Rector/BinaryOp/ResponseStatusCodeRector.php#L107-L108

processAssertMethodCall() then calls getArgs() without a first-class callable check, and the php-parser assertion fails:

https://github.com/rectorphp/rector-symfony/blob/5e4e27ac2f9beb1ecdb268dddf5e7e5a3954cc0d/rules/CodeQuality/Rector/BinaryOp/ResponseStatusCodeRector.php#L179

[ERROR] Could not process "src/Example.php" file, due to:
        "System error: "assert(!$this->isPartialFunctionApplication())"

        Stack trace:
        #0 vendor/rector/rector/vendor/nikic/php-parser/lib/PhpParser/Node/Expr/CallLike.php(50): assert()
        #1 vendor/rector/rector/vendor/rector/rector-symfony/rules/CodeQuality/Rector/BinaryOp/ResponseStatusCodeRector.php(155): PhpParser\Node\Expr\CallLike->getArgs()
        #2 vendor/rector/rector/vendor/rector/rector-symfony/rules/CodeQuality/Rector/BinaryOp/ResponseStatusCodeRector.php(105): Rector\Symfony\CodeQuality\Rector\BinaryOp\ResponseStatusCodeRector->processAssertMethodCall()
        #3 vendor/rector/rector/vendor/rector/rector-symfony/rules/CodeQuality/Rector/BinaryOp/ResponseStatusCodeRector.php(94): Rector\Symfony\CodeQuality\Rector\BinaryOp\ResponseStatusCodeRector->processMethodCall()

In Rector 2.5.7 the message is assert(!$this->isFirstClassCallable()). The crash needs zend.assertions = 1, which is the PHP CLI default in development setups.

Minimal PHP Code Causing Issue

<?php

final class Validator
{
    public function assert(mixed $value): string
    {
        return (string) $value;
    }
}

$validator = new Validator();
$values = array_map($validator->assert(...), ['a', 'b']);
<?php

use Rector\Config\RectorConfig;
use Rector\Symfony\CodeQuality\Rector\BinaryOp\ResponseStatusCodeRector;

return RectorConfig::configure()
    ->withPaths([__DIR__ . '/src'])
    ->withRules([ResponseStatusCodeRector::class]);

Run: php -d zend.assertions=1 vendor/bin/rector process --dry-run

Expected Behaviour

Rector should skip the node. A first-class callable has no arguments to inspect. A guard at the start of processMethodCall() would fix it. isPartialFunctionApplication() also covers first-class callables in the php-parser version that Rector 2.8.3 bundles:

if ($methodCall->isPartialFunctionApplication()) {
    return null;
}

Activity

  1. TomasVotruba commented on Nov 3, 2026

    @TomasVotruba
    Member

    Thanks for reporting, fixed in rectorphp/rector-symfony#1103

  2. devnix commented on Nov 3, 2026

    @devnix
    Author

    Thank you @TomasVotruba!

  3. devnix commented on Nov 4, 2026

    @devnix
    Author

    Some context on how I hit this, in case it helps others: I switched the default PHP CLI on my machine from 8.3 (zend.assertions = -1) to 8.4 (zend.assertions = 1). The Rector cache hid the crash. The affected files were already cached from runs under 8.3, and the cache stays valid after the PHP switch, so only a run without cache (a fresh worktree, or --clear-cache) showed it.

    This also points to a gap in CI. rector-src runs its tests with assertions enabled on purpose, for this exact case:

    https://github.com/rectorphp/rector-src/blob/0532a8977bfbf91008d5e20fdeb568b710a7ca83/.github/workflows/tests.yaml#L29-L35

    The workflows that run the rector-symfony tests don't set it: neither code_analysis.yaml here nor packages_tests.yaml in rector-src. setup-php uses the production php.ini by default, so assert() never runs there. With assertions disabled, getArgs() returns [VariadicPlaceholder], the old code returns null, and the new skip_first_class_callable_assert.php.inc fixture passes even without the fix.

    Would you accept a PR that adds ini-values: zend.assertions=1 to the setup-php step of both workflows? The other rule packages are probably in the same situation.

  4. TomasVotruba commented on Nov 4, 2026

    @TomasVotruba
    Member

    We do not run assert exactly for this reason. It runs/doesn't run, depends.
    Instead we prefer hard failure, exception and fixed bugs.

  5. devnix commented on Nov 5, 2026

    @devnix
    Author

    Fair enough, and for Rector's own code I agree: an exception beats assert().

    The catch is that this assert() isn't Rector's but php-parser's, so there's no exception to switch to. With assertions off, the rule didn't fail at all. It returned null by luck, and the new fixture passes even without the fix. Running the tests with zend.assertions=1 is the only way to get the hard failure you're after.

    Plenty of people run with assertions on, too: it's the PHP default when there's no php.ini (the official Docker images don't ship one), and php.ini-development sets it as well. That's how I ran into it.

    rector-src already enables it in tests.yaml and three other workflows, but not in packages_tests.yaml or the e2e ones, and none of the rule packages do. If you're open to it, I'm happy to send PRs adding ini-values: zend.assertions=1 to those setup-php steps, maybe with a check in the test bootstrap so a new workflow can't miss it.

  6. samsonasik commented on Nov 5, 2026

    @samsonasik
    Member
  7. samsonasik commented on Nov 5, 2026

    @samsonasik
    Member

    Oh i see, in package tests seems not exists now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions