stdClass{field: T} object shapes are a Phan-only notationstdClass{name: string} as StdClassShapeType and uses the
fields for property access, not for argument compatibility. Any stdClass
is accepted at the call site because the shape type casts as \stdClass.
Honouring the spelling therefore shows up *inside* the function: $obj->name
is string, so takesInt($obj->name) is the enforcement probe. A call-site
mismatch is the wrong question — Phan is silent there by design.
Other analyzers do not recognize the class-name-plus-braces form (PHPStan
parse-errors; Psalm invalid-docblock) and fall back to the native
\stdClass typehint, where $obj->name is untyped.
References:
- Phan Language/Type/StdClassShapeType.php (stdClass{field: T})
- Phan Language/Type.php shape-component parse for stdClass| Analyzer | Version | Result | Diagnostics |
|---|---|---|---|
| phan | 6.0.7Phan 6.0.7 php-ast version 1.1.3 PHP version used to run Phan: 8.5.9 | Enforced | phpdoc_advanced_phan_object_shape.php:32: Argument 1 ($value) is $obj->name of type string but \Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesInt() takes int defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/phpdoc_advanced_phan_object_shape.php:39 [PhanTypeMismatchArgument]
|
| phpstan | 2.2.8PHPStan - PHP Static Analysis Tool 2.2.8 | Unrecognized reported Lv.max | phpdoc_advanced_phan_object_shape.php:27: PHPDoc tag @param has invalid value (stdClass{name: string} $obj): Unexpected token "{", expected variable at offset 22 on line 2 [reported-from-level=2]
phpdoc_advanced_phan_object_shape.php:31: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesString expects string, mixed given. [reported-from-level=10]
phpdoc_advanced_phan_object_shape.php:32: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesInt expects int, mixed given. [reported-from-level=10]With strict-rulesphpdoc_advanced_phan_object_shape.php:27: PHPDoc tag @param has invalid value (stdClass{name: string} $obj): Unexpected token "{", expected variable at offset 22 on line 2
phpdoc_advanced_phan_object_shape.php:31: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesString expects string, mixed given.
phpdoc_advanced_phan_object_shape.php:32: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesInt expects int, mixed given.
Expectation diffLine 31: Expected valid value to be accepted, got ["Parameter #1 $value of function Conformance\\Tests\\PhpdocAdvancedPhanObjectShape\\takesString expects string, mixed given. [reported-from-level=10]"] |
| psalm | 6.16.1Psalm 6.16.1@f1f5de594dc76faf8784e02d3dc4716c91c6f6ac next: 7.0.0-beta19 |
Unrecognized (pzoom≠) | phpdoc_advanced_phan_object_shape.php:27: Unexpected brace character in docblock for Conformance\Tests\PhpdocAdvancedPhanObjectShape\acceptsObjectShape [InvalidDocblock] phpdoc_advanced_phan_object_shape.php:31: Argument 1 of Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesString cannot be mixed, expecting string [MixedArgument] phpdoc_advanced_phan_object_shape.php:32: Argument 1 of Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesInt expects int, but string provided [InvalidScalarArgument] pzoom (Psalm port)phpdoc_advanced_phan_object_shape.php:29: Parameter $obj has wrong type 'array{name: string}', should be 'stdClass' [MismatchingDocblockParamType]
phpdoc_advanced_phan_object_shape.php:31: Cannot access property $name on array{name: string} [InvalidPropertyFetch]
phpdoc_advanced_phan_object_shape.php:31: Argument 1 of takesString cannot be mixed, expecting string [MixedArgument]
phpdoc_advanced_phan_object_shape.php:32: Argument 1 of takesInt cannot be mixed, expecting int [MixedArgument]
phpdoc_advanced_phan_object_shape.php:47: Argument 1 of acceptsObjectShape expects array{name: string}, but stdClass provided [InvalidArgument]
Expectation diffLine 31: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhanObjectShape\\takesString cannot be mixed, expecting string [MixedArgument]"] |
| mago | 1.46.0mago 1.46.0 | Incidental (1/1) | phpdoc_advanced_phan_object_shape.php:31: Invalid argument type for argument #1 of `Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesString`: expected `string`, but found `mixed`. [mixed-argument] phpdoc_advanced_phan_object_shape.php:32: Invalid argument type for argument #1 of `Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesInt`: expected `int`, but found `mixed`. [mixed-argument]
Expectation diffLine 31: Expected valid value to be accepted, got ["Invalid argument type for argument #1 of `Conformance\\Tests\\PhpdocAdvancedPhanObjectShape\\takesString`: expected `string`, but found `mixed`. [mixed-argument]"] |
| mir | 0.70.1mir 0.70.1 | Not enforced | No diagnostics reported.
|
| phpantom | 0.9.0phpantom_lsp 0.9.0 | Unrecognized | phpdoc_advanced_phan_object_shape.php:27: Class 'Conformance\Tests\PhpdocAdvancedPhanObjectShape\stdClass{name: string}' not found [unknown_class]
|
| intelephense | 1.18.5intelephense 1.18.5 | Unrecognized | phpdoc_advanced_phan_object_shape.php:27: PHPDoc undefined type 'Conformance\Tests\PhpdocAdvancedPhanObjectShape\stdClass'. [P1133] phpdoc_advanced_phan_object_shape.php:27: PHPDoc parse error. Unexpected token. [P1129] phpdoc_advanced_phan_object_shape.php:31: Expected type 'string'. Found 'mixed'. [P1006] phpdoc_advanced_phan_object_shape.php:32: Expected type 'int'. Found 'mixed'. [P1006]
Expectation diffLine 31: Expected valid value to be accepted, got ["Expected type 'string'. Found 'mixed'. [P1006]"] |
| phpy | 0.2.0phpy 0.2.0 | Not enforced | No diagnostics reported.
|
| qodana | 262.8665.325Qodana 262.8665.325 | Not measured | No diagnostics reported. Notes: phpdoc_advanced_phan_object_shape.php changed after the Qodana report was produced, so the report cannot answer for it. Run Inspect Code in PhpStorm and re-run this tool. |
| noverify | 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 | Unrecognized ⚠ 1 false positive | phpdoc_advanced_phan_object_shape.php:12: Class or interface named \Conformance\Tests\PhpdocAdvancedPhanObjectShape\stdClass does not exist [undefinedClass]
phpdoc_advanced_phan_object_shape.php:29: param $obj miss matched with phpdoc type <<stdClass{name: string}>> [funcParamTypeMissMatch]
phpdoc_advanced_phan_object_shape.php:31: Property {\Conformance\Tests\PhpdocAdvancedPhanObjectShape\stdClass|\stdClass}->name does not exist [undefinedProperty]
phpdoc_advanced_phan_object_shape.php:32: Property {\Conformance\Tests\PhpdocAdvancedPhanObjectShape\stdClass|\stdClass}->name does not exist [undefinedProperty]
Expectation diffLine 31: Expected valid value to be accepted, got ["Property {\\Conformance\\Tests\\PhpdocAdvancedPhanObjectShape\\stdClass|\\stdClass}->name does not exist [undefinedProperty]"]
Line 12: Unexpected errors ["Class or interface named \\Conformance\\Tests\\PhpdocAdvancedPhanObjectShape\\stdClass does not exist [undefinedClass]"] |
| steins | 0.1.5steins 0.1.5 (2026-08-12 revision 39b4cc1) | Widened to mixed | No diagnostics reported.
Notes: steins-phpdoc's object-shape grammar only fires on the literal `object` keyword before `{` (parser.rs), not an arbitrary class name, so `stdClass{name: string}` fails to parse as a shape and the whole annotation drops to no envelope (ADR-0029) — the per-property constraint is never checked. |
| phpstan-strict | 2.2.8PHPStan - PHP Static Analysis Tool 2.2.8 | Unrecognized | phpdoc_advanced_phan_object_shape.php:27: PHPDoc tag @param has invalid value (stdClass{name: string} $obj): Unexpected token "{", expected variable at offset 22 on line 2
phpdoc_advanced_phan_object_shape.php:31: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesString expects string, mixed given.
phpdoc_advanced_phan_object_shape.php:32: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesInt expects int, mixed given.
Expectation diffLine 31: Expected valid value to be accepted, got ["Parameter #1 $value of function Conformance\\Tests\\PhpdocAdvancedPhanObjectShape\\takesString expects string, mixed given."] |
| psalm-next | 7.0.0-beta19Psalm 7.0.0-beta19@7e751c06a756fa64dc4c759c09fe4a173afcb433 | Unrecognized | phpdoc_advanced_phan_object_shape.php:27: Unexpected brace character in type Conformance\Tests\PhpdocAdvancedPhanObjectShape\stdClass in docblock for Conformance\Tests\PhpdocAdvancedPhanObjectShape\acceptsObjectShape [InvalidDocblock] phpdoc_advanced_phan_object_shape.php:31: Argument 1 of Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesString cannot be mixed, expecting string [MixedArgument] phpdoc_advanced_phan_object_shape.php:32: Argument 1 of Conformance\Tests\PhpdocAdvancedPhanObjectShape\takesInt expects int, but string provided [InvalidScalarArgument]
Expectation diffLine 31: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhanObjectShape\\takesString cannot be mixed, expecting string [MixedArgument]"] |
<?php
declare(strict_types=1);
namespace Conformance\Tests\PhpdocAdvancedPhanObjectShape;
/**
* `stdClass{field: T}` object shapes are a Phan-only notation.
*
* Phan parses `stdClass{name: string}` as `StdClassShapeType` and uses the
* fields for property access, not for argument compatibility. Any `stdClass`
* is accepted at the call site because the shape type casts as `\stdClass`.
* Honouring the spelling therefore shows up *inside* the function: `$obj->name`
* is `string`, so `takesInt($obj->name)` is the enforcement probe. A call-site
* mismatch is the wrong question — Phan is silent there by design.
*
* Other analyzers do not recognize the class-name-plus-braces form (PHPStan
* parse-errors; Psalm invalid-docblock) and fall back to the native
* `\stdClass` typehint, where `$obj->name` is untyped.
*
* References:
* - Phan `Language/Type/StdClassShapeType.php` (`stdClass{field: T}`)
* - Phan `Language/Type.php` shape-component parse for `stdClass`
*/
/**
* @param stdClass{name: string} $obj
*/
function acceptsObjectShape(\stdClass $obj): void // T: stdClass{name: string}
{
takesString($obj->name); // V
takesInt($obj->name); // E?: name is string under stdClass{name: string}, not int
}
function takesString(string $value): void
{
}
function takesInt(int $value): void
{
}
// Any stdClass is accepted at the call site, even without the field — the
// shape is not an argument check. This only gives the function a value.
$valid = new \stdClass();
$valid->name = 'Ada';
acceptsObjectShape($valid); // V