Skip to content

[Php81] Skip NullToStrictStringFuncCallArgRector for magic __get() property access#7992

Merged
TomasVotruba merged 1 commit into
mainfrom
claude/rector-fixture-latest-issue-5ueLN
May 16, 2026
Merged

[Php81] Skip NullToStrictStringFuncCallArgRector for magic __get() property access#7992
TomasVotruba merged 1 commit into
mainfrom
claude/rector-fixture-latest-issue-5ueLN

Conversation

@TomasVotruba
Copy link
Copy Markdown
Member

@TomasVotruba TomasVotruba commented May 16, 2026

Fixes rectorphp/rector#9762

Problem

NullToStrictStringFuncCallArgRector incorrectly wraps a magic property access in a (string) cast even when the value is already guarded by a truthy if check:

class Entity
{
    protected array $_fields = [];

    public function __get(string $field)
    {
        $value = null;
        if (isset($this->_fields[$field])) {
            $value = &$this->_fields[$field];
        }
        return $value;
    }
}

$entity = new Entity();
if ($entity->someProp) {
    // incorrectly became: preg_replace('/\r\n|\n|\r/', '', (string) $entity->someProp);
    preg_replace('/\r\n|\n|\r/', '', $entity->someProp);
}

Root Cause

When __get() has no return type hint, PHPStan resolves any magic property access to ErrorType (shown as *ERROR* in dumpType). Since ErrorType extends MixedType, the existing shouldSkipType() check treats it as a plain MixedType and does not skip. The isAnErrorType() guard also misses it because it inspects the native type (plain MixedType) and its parent-scope fallback only fires inside closures/functions — at the top level getParentScope() returns null.

Fix

In shouldSkipValue(), check upfront whether the argument is a PropertyFetch on a class that declares __get(). If so, the type is unresolvable via static analysis and the cast would be wrong.

private function isPropertyFetchOnClassWithMagicGet(Expr $expr): bool
{
    if (! $expr instanceof PropertyFetch) {
        return false;
    }

    $varType = $this->nodeTypeResolver->getType($expr->var);
    if (! $varType instanceof ObjectType) {
        return false;
    }

    if (! $this->reflectionProvider->hasClass($varType->getClassName())) {
        return false;
    }

    return $this->reflectionProvider->getClass($varType->getClassName())->hasMethod('__get');
}

hasMethod('__get') resolves through the full inheritance chain, so it also covers classes that inherit __get() from a parent (e.g. CakePHP's EntityTrait).

Test

Added skip_magic_get_property_in_if_truthy_check.php.inc fixture reproducing the exact scenario from the issue.

@TomasVotruba TomasVotruba force-pushed the claude/rector-fixture-latest-issue-5ueLN branch from a5eadd8 to c49acec Compare May 16, 2026 20:53
@TomasVotruba
Copy link
Copy Markdown
Member Author

Looks good 🙏

@TomasVotruba TomasVotruba merged commit 7625651 into main May 16, 2026
78 checks passed
@TomasVotruba TomasVotruba deleted the claude/rector-fixture-latest-issue-5ueLN branch May 16, 2026 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect behavior of NullToStrictStringFuncCallArgRector

2 participants