$class::$prop when $class is a class-string, not a bare class nameHolder::$count is a public int. Through class-string<Holder> the same
read should be int, a string write should be rejected, and the private
$secret should stay inaccessible. get_class($holder) is the same
class-string; Psalm 6.x used to analyse that read as a made-up object
property (InvalidPropertyFetch on $__fake_var).
Source: vimeo/psalm#12040, from #3609 / #4198.
References:
- psalm#12040| Analyzer | Version | Result | Diagnostics |
|---|---|---|---|
| phan | 6.0.10Phan 6.0.10 php-ast version 1.1.3 PHP version used to run Phan: 8.5.11 | Enforced | regressions_variable_class_static_property.php:52: Assigning 'no' of type string to property but \Conformance\Tests\RegressionsVariableClassStaticProperty\Holder::$count is int [PhanTypeMismatchPropertyReal] regressions_variable_class_static_property.php:60: Cannot access private property \Conformance\Tests\RegressionsVariableClassStaticProperty\Holder::$secret defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/regressions_variable_class_static_property.php:26 [PhanAccessPropertyPrivate]
|
| phpstan | 2.3.1PHPStan - PHP Static Analysis Tool 2.3.1 | Partly enforced (2/4) | No diagnostics reported.
Expectation diffLine 52: Expected 1 error(s) Line 60: Expected 1 error(s) |
| psalm | 6.20.0Psalm 6.20.0@804f03c0c183d149fcd4b8c7c0c9b3fbf836d34c next: 7.0.0-rc2 |
Enforced (pzoom≠) | regressions_variable_class_static_property.php:52: Conformance\Tests\RegressionsVariableClassStaticProperty\Holder::$count with declared type 'int' cannot be assigned type ''no'' [InvalidPropertyAssignmentValue] regressions_variable_class_static_property.php:60: Cannot access private property Conformance\Tests\RegressionsVariableClassStaticProperty\Holder::$secret [InaccessibleProperty] pzoom (Psalm port)regressions_variable_class_static_property.php:60: Cannot access private property Conformance\Tests\RegressionsVariableClassStaticProperty\Holder::$secret [InaccessibleProperty] regressions_variable_class_static_property.php:66: Cannot access property $count on class-string<Conformance\Tests\RegressionsVariableClassStaticProperty\Holder> [InvalidPropertyFetch] regressions_variable_class_static_property.php:66: Argument 1 of takesInt cannot be mixed, expecting int [MixedArgument]
|
| mago | 1.57.0mago 1.57.0 | Partly enforced (3/4) | regressions_variable_class_static_property.php:52: Invalid property assignment value [invalid-property-assignment-value] regressions_variable_class_static_property.php:60: Cannot read private property `$secret` from class `Conformance\Tests\RegressionsVariableClassStaticProperty\Holder`. [invalid-property-read] regressions_variable_class_static_property.php:60: Argument #1 passed to function `Conformance\Tests\RegressionsVariableClassStaticProperty\takesInt` has type `never`, meaning it cannot produce a value. [no-value] regressions_variable_class_static_property.php:66: Invalid argument type for argument #1 of `Conformance\Tests\RegressionsVariableClassStaticProperty\takesInt`: expected `int`, but found `mixed`. [mixed-argument]
Expectation diffLine 66: Expected silence (quiet probe), got ["Invalid argument type for argument #1 of `Conformance\\Tests\\RegressionsVariableClassStaticProperty\\takesInt`: expected `int`, but found `mixed`. [mixed-argument]"] |
| mir | 0.87.0mir 0.87.0 | Partly enforced (3/4) | regressions_variable_class_static_property.php:52: InvalidPropertyAssignment: Property $count expects 'int', cannot assign '"no"' [MIR0206] regressions_variable_class_static_property.php:60: InaccessibleProperty: Cannot access property Conformance\Tests\RegressionsVariableClassStaticProperty\Holder::$secret [MIR0014] regressions_variable_class_static_property.php:66: MixedArgument: Argument $value of takesInt() is mixed [MIR1200]
Expectation diffLine 66: Expected silence (quiet probe), got ["MixedArgument: Argument $value of takesInt() is mixed [MIR1200]"] |
| phpantom | 0.11.1phpantom_lsp 0.11.1 | Partly enforced (3/4) | regressions_variable_class_static_property.php:60: Cannot access private static property Conformance\Tests\RegressionsVariableClassStaticProperty\Holder::$secret from outside its declaring class [invalid_member_access]
Expectation diffLine 52: Expected 1 error(s) |
| intelephense | 1.18.5intelephense 1.18.5 | Partly enforced (1/4) | regressions_variable_class_static_property.php:43: Expected type 'int'. Found 'mixed'. [P1006] regressions_variable_class_static_property.php:60: Expected type 'int'. Found 'mixed'. [P1006] regressions_variable_class_static_property.php:60: Cannot access private property Conformance\Tests\RegressionsVariableClassStaticProperty\Holder::$secret from global scope. [P1080] regressions_variable_class_static_property.php:66: Expected type 'int'. Found 'mixed'. [P1006]
Expectation diffLine 52: Expected 1 error(s) Line 43: Expected silence (quiet probe), got ["Expected type 'int'. Found 'mixed'. [P1006]"] Line 66: Expected silence (quiet probe), got ["Expected type 'int'. Found 'mixed'. [P1006]"] |
| phpactor | 2026.10.07.0Phpactor 2026.10.07.0 | Partly enforced (2/4) | No diagnostics reported.
Expectation diffLine 52: Expected 1 error(s) Line 60: Expected 1 error(s) |
| phpy | 1.0.19409phpy 1.0.19409 | Enforced | regressions_variable_class_static_property.php:52: Cannot implicitly convert 'string' to 'int' regressions_variable_class_static_property.php:60: Member has private visibility and is not accessible from the current context.
|
| qodana | 262.10968.76PhpStorm 262.10968.76 | Partly enforced (3/4) | regressions_variable_class_static_property.php:52: Property must be of type 'int', 'string' provided [PhpStrictTypeCheckingInspection]
Expectation diffLine 60: Expected 1 error(s) |
| noverify | 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 | Unrecognized | regressions_variable_class_static_property.php:41: param $class miss matched with phpdoc type <<class-string<Holder>>> [funcParamTypeMissMatch] regressions_variable_class_static_property.php:49: param $class miss matched with phpdoc type <<class-string<Holder>>> [funcParamTypeMissMatch] regressions_variable_class_static_property.php:58: param $class miss matched with phpdoc type <<class-string<Holder>>> [funcParamTypeMissMatch]
Expectation diffLine 52: Expected 1 error(s) Line 60: Expected 1 error(s) |
| steins | 0.1.8steins 0.1.8 (2026-09-21 revision 76306a9) | Partly enforced (2/4) | No diagnostics reported.
Expectation diffLine 52: Expected 1 error(s) Line 60: Expected 1 error(s) |
| phpstan-strict | 2.3.1PHPStan - PHP Static Analysis Tool 2.3.1 | Partly enforced (2/4) | No diagnostics reported.
Expectation diffLine 52: Expected 1 error(s) Line 60: Expected 1 error(s) |
| psalm-next | 7.0.0-rc2Psalm 7.0.0-rc2@20339e3fe5b8d03bcd8061030eb2a83ad50e5783 | Enforced | regressions_variable_class_static_property.php:52: Conformance\Tests\RegressionsVariableClassStaticProperty\Holder::$count with declared type 'int' cannot be assigned type ''no'' [InvalidPropertyAssignmentValue] regressions_variable_class_static_property.php:60: Cannot access private property Conformance\Tests\RegressionsVariableClassStaticProperty\Holder::$secret [InaccessibleProperty]
|
<?php
declare(strict_types=1);
namespace Conformance\Tests\RegressionsVariableClassStaticProperty;
/**
* `$class::$prop` when `$class` is a class-string, not a bare class name.
*
* `Holder::$count` is a public int. Through `class-string<Holder>` the same
* read should be int, a string write should be rejected, and the private
* `$secret` should stay inaccessible. `get_class($holder)` is the same
* class-string; Psalm 6.x used to analyse that read as a made-up object
* property (`InvalidPropertyFetch` on `$__fake_var`).
*
* Source: vimeo/psalm#12040, from #3609 / #4198.
*
* References:
* - https://github.com/vimeo/psalm/pull/12040
*/
final class Holder
{
public static int $count = 1;
private static int $secret = 2;
public static function secret(): int
{
return self::$secret;
}
}
function takesInt(int $value): void
{
}
/**
* @param class-string<Holder> $class
*/
function readCount(string $class): void // T: class-string<Holder>
{
takesInt($class::$count); // Q: class-string<Holder>::$count is int
}
/**
* @param class-string<Holder> $class
*/
function writeCount(string $class): void // T: class-string<Holder>
{
$class::$count = 2; // V: int write matches the declared type
$class::$count = 'no'; // E: string is not int
}
/**
* @param class-string<Holder> $class
*/
function readSecret(string $class): void // T: class-string<Holder>
{
takesInt($class::$secret); // E: $secret is private
}
function fromGetClass(Holder $holder): void
{
$class = get_class($holder);
takesInt($class::$count); // Q: get_class() is class-string<Holder>
}
takesInt(Holder::$count); // V: the same read with a bare class name