← All results

stdClass{field: T} object shapes are a Phan-only notation

Group: Advanced PHPDoc types · Category: ecosystem · File: phpdoc_advanced_phan_object_shape.php

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

Analyzer results

AnalyzerVersionResultDiagnostics
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]
  • Recognition: spelling resolved
  • Enforcement: 1/1 of the expected violations reported
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-rules
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.
  • Recognition: spelling not resolved — reported on declaration line(s) 27
  • Enforcement: 1/1 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 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]
  • Recognition: spelling not resolved — reported on declaration line(s) 27
  • Enforcement: 1/1 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 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]
  • Recognition: spelling resolved
  • Enforcement: 1/1 of the expected violations reported — incidental, since values the type admits were also rejected on line(s) 31
Expectation diff
Line 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.

  • Recognition: spelling resolved
  • Enforcement: 0/1 of the expected violations 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]
  • Recognition: spelling not resolved — reported on declaration line(s) 27
  • Enforcement: 0/1 of the expected violations reported — incidental, since the spelling was not resolved
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]
  • Recognition: spelling not resolved — reported on declaration line(s) 27
  • Enforcement: 1/1 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 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.

  • Recognition: spelling resolved
  • Enforcement: 0/1 of the expected violations 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]
  • Recognition: spelling not resolved — reported on declaration line(s) 29
  • Enforcement: 1/1 of the expected violations reported — incidental, since the spelling was not resolved
  • False positives: line(s) 12
Expectation diff
Line 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.

  • Recognition: spelling resolved
  • Enforcement: 0/1 of the expected violations 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.
  • Recognition: spelling not resolved — reported on declaration line(s) 27
  • Enforcement: 1/1 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 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]
  • Recognition: spelling not resolved — reported on declaration line(s) 27
  • Enforcement: 1/1 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 31: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhanObjectShape\\takesString cannot be mixed, expecting string [MixedArgument]"]

Source

<?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