__benevolent<int|string>__benevolent<int|string> to a native int or
string parameter is silent; the same value typed as a plain int|string
is not. The inbound direction is unchanged — a float is still not in the
union. PHPStan uses the spelling in stubs (__benevolent<CurlHandle|false>);
array-key is the same constructor without the wrapper.
Honouring the wrapper is silence on the member-assignment lines. Tools that
do not know the spelling reject those calls as an unknown class or as an
ordinary union; that is not enforcement of __benevolent.
References:
- PHPStan TypeNodeResolver __benevolent → TypeUtils::toBenevolentUnion
- PHPStan config-reference: checkBenevolentUnionTypes (off by default)
- phpstan#6440| 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 | Unrecognized | phpdoc_advanced_phpstan_benevolent_union.php:38: Return type of returnsBenevolent() is undeclared type \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> [PhanUndeclaredTypeReturnType] phpdoc_advanced_phpstan_benevolent_union.php:40: Returning 'key' of type string but returnsBenevolent() is declared to return \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> (no real type) (the inferred real return type has nothing in common with the declared phpdoc return type) [PhanTypeMismatchReturnProbablyReal] phpdoc_advanced_phpstan_benevolent_union.php:40: Returning 1 of type int (value: 1) but returnsBenevolent() is declared to return \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> (no real type) (the inferred real return type has nothing in common with the declared phpdoc return type) [PhanTypeMismatchReturnProbablyReal] phpdoc_advanced_phpstan_benevolent_union.php:54: Parameter $value has undeclared type \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> [PhanUndeclaredTypeParameter] phpdoc_advanced_phpstan_benevolent_union.php:59: Argument 1 ($value) is 1 of type int (value: 1) but \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent() takes \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> (no real type) defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/phpdoc_advanced_phpstan_benevolent_union.php:54 (the inferred real argument type has nothing in common with the parameter's phpdoc type) [PhanTypeMismatchArgumentProbablyReal] phpdoc_advanced_phpstan_benevolent_union.php:60: Argument 1 ($value) is 'key' of type string but \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent() takes \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> (no real type) defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/phpdoc_advanced_phpstan_benevolent_union.php:54 (the inferred real argument type has nothing in common with the parameter's phpdoc type) [PhanTypeMismatchArgumentProbablyReal] phpdoc_advanced_phpstan_benevolent_union.php:63: Argument 1 ($value) is 1.5 of type float (value: 1.5) but \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent() takes \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> (no real type) defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/phpdoc_advanced_phpstan_benevolent_union.php:54 (the inferred real argument type has nothing in common with the parameter's phpdoc type) [PhanTypeMismatchArgumentProbablyReal] phpdoc_advanced_phpstan_benevolent_union.php:66: Argument 1 ($value) is returnsBenevolent() of type \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent|\Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> but \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesInt() takes int defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/phpdoc_advanced_phpstan_benevolent_union.php:27 [PhanTypeMismatchArgument] phpdoc_advanced_phpstan_benevolent_union.php:67: Argument 1 ($value) is returnsBenevolent() of type \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent|\Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> but \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesString() takes string defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/phpdoc_advanced_phpstan_benevolent_union.php:31 [PhanTypeMismatchArgument]
Expectation diffLine 40: Expected valid value to be accepted, got ["Returning 'key' of type string but returnsBenevolent() is declared to return \\Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent<int|string> (no real type) (the inferred real return type has nothing in common with the declared phpdoc return type) [PhanTypeMismatchReturnProbablyReal]","Returning 1 of type int (value: 1) but returnsBenevolent() is declared to return \\Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent<int|string> (no real type) (the inferred real return type has nothing in common with the declared phpdoc return type) [PhanTypeMismatchReturnProbablyReal]"] Line 59: Expected valid value to be accepted, got ["Argument 1 ($value) is 1 of type int (value: 1) but \\Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\acceptsBenevolent() takes \\Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent<int|string> (no real type) defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/phpdoc_advanced_phpstan_benevolent_union.php:54 (the inferred real argument type has nothing in common with the parameter's phpdoc type) [PhanTypeMismatchArgumentProbablyReal]"] Line 60: Expected valid value to be accepted, got ["Argument 1 ($value) is 'key' of type string but \\Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\acceptsBenevolent() takes \\Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent<int|string> (no real type) defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/phpdoc_advanced_phpstan_benevolent_union.php:54 (the inferred real argument type has nothing in common with the parameter's phpdoc type) [PhanTypeMismatchArgumentProbablyReal]"] |
| phpstan | 2.2.11PHPStan - PHP Static Analysis Tool 2.2.11 | Enforced reported Lv.5+ | phpdoc_advanced_phpstan_benevolent_union.php:63: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent expects (int|string), float given. [reported-from-level=5] phpdoc_advanced_phpstan_benevolent_union.php:72: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesInt expects int, int|string given. [reported-from-level=7] phpdoc_advanced_phpstan_benevolent_union.php:73: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesString expects string, int|string given. [reported-from-level=7] With strict-rulesphpdoc_advanced_phpstan_benevolent_union.php:63: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent expects (int|string), float given. phpdoc_advanced_phpstan_benevolent_union.php:72: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesInt expects int, int|string given. phpdoc_advanced_phpstan_benevolent_union.php:73: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesString expects string, int|string given.
|
| psalm | 6.16.1Psalm 6.16.1@f1f5de594dc76faf8784e02d3dc4716c91c6f6ac next: 7.0.0-beta19 |
Unrecognized (pzoom≠) | phpdoc_advanced_phpstan_benevolent_union.php:36: Docblock-defined class, interface or enum named Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent does not exist [UndefinedDocblockClass] phpdoc_advanced_phpstan_benevolent_union.php:36: The declared return type 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>' for Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\returnsBenevolent is incorrect, got ''key'|1' [InvalidReturnType] phpdoc_advanced_phpstan_benevolent_union.php:40: The inferred type ''key'|1' does not match the declared return type 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>' for Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\returnsBenevolent [InvalidReturnStatement] phpdoc_advanced_phpstan_benevolent_union.php:52: Docblock-defined class, interface or enum named Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent does not exist [UndefinedDocblockClass] phpdoc_advanced_phpstan_benevolent_union.php:59: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent expects Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>, but 1 provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:60: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent expects Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>, but 'key' provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:63: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent expects Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>, but float(1.5) provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:66: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesInt expects int, but Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:67: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesString expects string, but Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:72: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesInt expects int, but int|string provided [InvalidScalarArgument] phpdoc_advanced_phpstan_benevolent_union.php:73: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesString expects string, but int|string provided [InvalidScalarArgument] pzoom (Psalm port)phpdoc_advanced_phpstan_benevolent_union.php:36: Docblock class Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent does not exist [UndefinedDocblockClass] phpdoc_advanced_phpstan_benevolent_union.php:40: The type 'key'|1 does not match the declared return type Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> [InvalidReturnStatement] phpdoc_advanced_phpstan_benevolent_union.php:54: Docblock class Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent does not exist [UndefinedDocblockClass] phpdoc_advanced_phpstan_benevolent_union.php:59: Argument 1 of acceptsBenevolent expects Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>, but 1 provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:60: Argument 1 of acceptsBenevolent expects Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>, but 'key' provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:63: Argument 1 of acceptsBenevolent expects Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>, but 1.5 provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:66: Argument 1 of takesInt expects int, but Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:67: Argument 1 of takesString expects string, but Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:72: Argument 1 of takesInt expects int, but possibly different type int|string provided [PossiblyInvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:73: Argument 1 of takesString expects string, but possibly different type int|string provided [PossiblyInvalidArgument]
Expectation diffLine 40: Expected valid value to be accepted, got ["The inferred type ''key'|1' does not match the declared return type 'Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent<int|string>' for Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\returnsBenevolent [InvalidReturnStatement]"] Line 59: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\acceptsBenevolent expects Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent<int|string>, but 1 provided [InvalidArgument]"] Line 60: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\acceptsBenevolent expects Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent<int|string>, but 'key' provided [InvalidArgument]"] |
| mago | 1.47.4mago 1.47.4 | Unrecognized | phpdoc_advanced_phpstan_benevolent_union.php:36: Cannot find class, interface, enum, or type alias `Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent`. [non-existent-class-like]
phpdoc_advanced_phpstan_benevolent_union.php:40: Invalid return type for function `Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\returnsBenevolent`: expected `unknown-ref(Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent)`, but found `int(1)|string('key')`. [invalid-return-statement]
phpdoc_advanced_phpstan_benevolent_union.php:52: Cannot find class, interface, enum, or type alias `Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent`. [non-existent-class-like]
phpdoc_advanced_phpstan_benevolent_union.php:59: Invalid argument type for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent`: expected `unknown-ref(Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent)`, but found `int(1)`. [invalid-argument]
phpdoc_advanced_phpstan_benevolent_union.php:60: Invalid argument type for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent`: expected `unknown-ref(Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent)`, but found `string('key')`. [invalid-argument]
phpdoc_advanced_phpstan_benevolent_union.php:63: Invalid argument type for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent`: expected `unknown-ref(Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent)`, but found `float(1.5)`. [invalid-argument]
phpdoc_advanced_phpstan_benevolent_union.php:66: Invalid argument type for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesInt`: expected `int`, but found `unknown-ref(Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent)`. [invalid-argument]
phpdoc_advanced_phpstan_benevolent_union.php:67: Invalid argument type for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesString`: expected `string`, but found `unknown-ref(Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent)`. [invalid-argument]
phpdoc_advanced_phpstan_benevolent_union.php:72: Possible argument type mismatch for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesInt`: expected `int`, but possibly received `int|string`. [possibly-invalid-argument]
phpdoc_advanced_phpstan_benevolent_union.php:73: Possible argument type mismatch for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesString`: expected `string`, but possibly received `int|string`. [possibly-invalid-argument]
Expectation diffLine 40: Expected valid value to be accepted, got ["Invalid return type for function `Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\returnsBenevolent`: expected `unknown-ref(Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent)`, but found `int(1)|string('key')`. [invalid-return-statement]"]
Line 59: Expected valid value to be accepted, got ["Invalid argument type for argument #1 of `Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\acceptsBenevolent`: expected `unknown-ref(Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent)`, but found `int(1)`. [invalid-argument]"]
Line 60: Expected valid value to be accepted, got ["Invalid argument type for argument #1 of `Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\acceptsBenevolent`: expected `unknown-ref(Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent)`, but found `string('key')`. [invalid-argument]"] |
| mir | 0.72.1mir 0.72.1 | Unrecognized | phpdoc_advanced_phpstan_benevolent_union.php:38: UndefinedDocblockClass: Docblock type 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent' does not exist [MIR1505] phpdoc_advanced_phpstan_benevolent_union.php:54: UndefinedDocblockClass: Docblock type 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent' does not exist [MIR1505] phpdoc_advanced_phpstan_benevolent_union.php:59: InvalidArgument: Argument $value of acceptsBenevolent() expects 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>', got '1' [MIR0201] phpdoc_advanced_phpstan_benevolent_union.php:60: InvalidArgument: Argument $value of acceptsBenevolent() expects 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>', got '"key"' [MIR0201] phpdoc_advanced_phpstan_benevolent_union.php:63: InvalidArgument: Argument $value of acceptsBenevolent() expects 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>', got '1.5' [MIR0201] phpdoc_advanced_phpstan_benevolent_union.php:66: InvalidArgument: Argument $value of takesInt() expects 'int', got 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>' [MIR0201] phpdoc_advanced_phpstan_benevolent_union.php:67: InvalidArgument: Argument $value of takesString() expects 'string', got 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>' [MIR0201] phpdoc_advanced_phpstan_benevolent_union.php:72: PossiblyInvalidArgument: Argument $value of takesInt() expects 'int', possibly different type 'int|string' provided [MIR0105] phpdoc_advanced_phpstan_benevolent_union.php:73: PossiblyInvalidArgument: Argument $value of takesString() expects 'string', possibly different type 'int|string' provided [MIR0105]
Expectation diffLine 59: Expected valid value to be accepted, got ["InvalidArgument: Argument $value of acceptsBenevolent() expects 'Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent<int|string>', got '1' [MIR0201]"] Line 60: Expected valid value to be accepted, got ["InvalidArgument: Argument $value of acceptsBenevolent() expects 'Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent<int|string>', got '\"key\"' [MIR0201]"] |
| phpantom | 0.10.0phpantom_lsp 0.10.0 | Widened to int|string | phpdoc_advanced_phpstan_benevolent_union.php:63: Argument 1 ($value) expects int|string, got 1.5 [type_mismatch_argument] phpdoc_advanced_phpstan_benevolent_union.php:72: Argument 1 ($value) expects int, got int|string (string does not satisfy int) [type_mismatch_argument] phpdoc_advanced_phpstan_benevolent_union.php:73: Argument 1 ($value) expects string, got int|string (int does not satisfy string) [type_mismatch_argument]
Notes: The float rejection is ordinary `int|string`, not the wrapper. Reverse assignment is silent because a plain `int|string` is silent at the same calls; `array-key` is kept as a named atom and does reject there. |
| intelephense | 1.18.5intelephense 1.18.5 | Unrecognized | phpdoc_advanced_phpstan_benevolent_union.php:36: PHPDoc undefined type 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent'. [P1133] phpdoc_advanced_phpstan_benevolent_union.php:52: PHPDoc undefined type 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent'. [P1133] phpdoc_advanced_phpstan_benevolent_union.php:66: Expected type 'int'. Found 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>'. [P1006] phpdoc_advanced_phpstan_benevolent_union.php:67: Expected type 'string'. Found 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>'. [P1006] phpdoc_advanced_phpstan_benevolent_union.php:72: Expected type 'int'. Found 'int|string'. [P1006] phpdoc_advanced_phpstan_benevolent_union.php:73: Expected type 'string'. Found 'int|string'. [P1006]
|
| phpactor | 2026.07.22.0Phpactor 2026.07.22.0 | Not enforced | No diagnostics reported.
|
| phpy | 0.2.0phpy 0.2.0 | Unrecognized | phpdoc_advanced_phpstan_benevolent_union.php:36: Use of unknown class: 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent' phpdoc_advanced_phpstan_benevolent_union.php:40: Return value of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\returnsBenevolent() is expected to be of type Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent, int|string returned phpdoc_advanced_phpstan_benevolent_union.php:52: Use of unknown class: 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent' phpdoc_advanced_phpstan_benevolent_union.php:59: Argument '1' passed to Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent() is expected to be of type Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent, int given phpdoc_advanced_phpstan_benevolent_union.php:60: Argument '1' passed to Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent() is expected to be of type Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent, string given phpdoc_advanced_phpstan_benevolent_union.php:63: Argument '1' passed to Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent() is expected to be of type Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent, float given phpdoc_advanced_phpstan_benevolent_union.php:66: Argument '1' passed to Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesInt() is expected to be of type int, Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent given phpdoc_advanced_phpstan_benevolent_union.php:67: Argument '1' passed to Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesString() is expected to be of type string, Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent given
Expectation diffLine 40: Expected valid value to be accepted, got ["Return value of Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\returnsBenevolent() is expected to be of type Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent, int|string returned"] Line 59: Expected valid value to be accepted, got ["Argument '1' passed to Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\acceptsBenevolent() is expected to be of type Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent, int given"] Line 60: Expected valid value to be accepted, got ["Argument '1' passed to Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\acceptsBenevolent() is expected to be of type Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent, string given"] |
| qodana | 262.9437.196Qodana 262.9437.196 | Unrecognized | phpdoc_advanced_phpstan_benevolent_union.php:36: Return type in PHPDoc does not match the actual return type [PhpReturnDocTypeMismatchInspection] phpdoc_advanced_phpstan_benevolent_union.php:36: Undefined class '__benevolent' [PhpUndefinedClassInspection] phpdoc_advanced_phpstan_benevolent_union.php:40: Return value type is not compatible with declared [PhpIncompatibleReturnTypeInspection] phpdoc_advanced_phpstan_benevolent_union.php:40: Return value type is not compatible with declared [PhpIncompatibleReturnTypeInspection] phpdoc_advanced_phpstan_benevolent_union.php:52: Undefined class '__benevolent' [PhpUndefinedClassInspection] phpdoc_advanced_phpstan_benevolent_union.php:59: Parameter '1' type is not compatible with declaration [PhpParamsInspection] phpdoc_advanced_phpstan_benevolent_union.php:60: Parameter ''key'' type is not compatible with declaration [PhpParamsInspection] phpdoc_advanced_phpstan_benevolent_union.php:63: Parameter '1.5' type is not compatible with declaration [PhpParamsInspection]
Expectation diffLine 40: Expected valid value to be accepted, got ["Return value type is not compatible with declared [PhpIncompatibleReturnTypeInspection]","Return value type is not compatible with declared [PhpIncompatibleReturnTypeInspection]"] Line 59: Expected valid value to be accepted, got ["Parameter '1' type is not compatible with declaration [PhpParamsInspection]"] Line 60: Expected valid value to be accepted, got ["Parameter ''key'' type is not compatible with declaration [PhpParamsInspection]"] |
| noverify | 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 | Unrecognized | phpdoc_advanced_phpstan_benevolent_union.php:36: Class or interface named \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent does not exist [undefinedClass] phpdoc_advanced_phpstan_benevolent_union.php:52: Class or interface named \Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent does not exist [undefinedClass]
|
| steins | 0.1.6steins 0.1.6 (2026-08-25 revision c13b4c7) | Widened to int|string | phpdoc_advanced_phpstan_benevolent_union.php:63: argument 1.5 to acceptsBenevolent() violates declared @param (int | string) $value — declared contract violation [phpdoc.param-mismatch]
Notes: The float rejection is ordinary `int|string`, not the wrapper. Reverse assignment is silent because a callee `@return` never reaches a bare call-expression argument in this column, as on `array-key`. |
| phpstan-strict | 2.2.11PHPStan - PHP Static Analysis Tool 2.2.11 | Enforced | phpdoc_advanced_phpstan_benevolent_union.php:63: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent expects (int|string), float given. phpdoc_advanced_phpstan_benevolent_union.php:72: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesInt expects int, int|string given. phpdoc_advanced_phpstan_benevolent_union.php:73: Parameter #1 $value of function Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesString expects string, int|string given.
|
| psalm-next | 7.0.0-beta19Psalm 7.0.0-beta19@7e751c06a756fa64dc4c759c09fe4a173afcb433 | Unrecognized | phpdoc_advanced_phpstan_benevolent_union.php:36: Docblock-defined class, interface or enum named Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent does not exist [UndefinedDocblockClass] phpdoc_advanced_phpstan_benevolent_union.php:36: The declared return type 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>' for Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\returnsBenevolent is incorrect, got ''key'|1' [InvalidReturnType] phpdoc_advanced_phpstan_benevolent_union.php:40: The inferred type ''key'|1' does not match the declared return type 'Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>' for Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\returnsBenevolent [InvalidReturnStatement] phpdoc_advanced_phpstan_benevolent_union.php:52: Docblock-defined class, interface or enum named Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent does not exist [UndefinedDocblockClass] phpdoc_advanced_phpstan_benevolent_union.php:59: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent expects Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>, but 1 provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:60: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent expects Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>, but 'key' provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:63: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\acceptsBenevolent expects Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string>, but float(1.5) provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:66: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesInt expects int, but Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:67: Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> cannot be cast to string [InvalidCast] phpdoc_advanced_phpstan_benevolent_union.php:67: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesString expects string, but Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\__benevolent<int|string> provided [InvalidArgument] phpdoc_advanced_phpstan_benevolent_union.php:72: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesInt expects int, but int|string provided [InvalidScalarArgument] phpdoc_advanced_phpstan_benevolent_union.php:73: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion\takesString expects string, but int|string provided [InvalidScalarArgument]
Expectation diffLine 40: Expected valid value to be accepted, got ["The inferred type ''key'|1' does not match the declared return type 'Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent<int|string>' for Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\returnsBenevolent [InvalidReturnStatement]"] Line 59: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\acceptsBenevolent expects Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent<int|string>, but 1 provided [InvalidArgument]"] Line 60: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\acceptsBenevolent expects Conformance\\Tests\\PhpdocAdvancedPhpstanBenevolentUnion\\__benevolent<int|string>, but 'key' provided [InvalidArgument]"] |
<?php
declare(strict_types=1);
namespace Conformance\Tests\PhpdocAdvancedPhpstanBenevolentUnion;
/**
* `__benevolent<int|string>`
*
* A PHPStan-only wrapper: the inner union is accepted wherever *any* of its
* members would be. Passing `__benevolent<int|string>` to a native `int` or
* `string` parameter is silent; the same value typed as a plain `int|string`
* is not. The inbound direction is unchanged — a float is still not in the
* union. PHPStan uses the spelling in stubs (`__benevolent<CurlHandle|false>`);
* `array-key` is the same constructor without the wrapper.
*
* Honouring the wrapper is silence on the member-assignment lines. Tools that
* do not know the spelling reject those calls as an unknown class or as an
* ordinary union; that is not enforcement of `__benevolent`.
*
* References:
* - PHPStan TypeNodeResolver `__benevolent` → TypeUtils::toBenevolentUnion
* - PHPStan config-reference: `checkBenevolentUnionTypes` (off by default)
* - https://github.com/phpstan/phpstan/discussions/6440
*/
function takesInt(int $value): void
{
}
function takesString(string $value): void
{
}
/**
* @return __benevolent<int|string>
*/
function returnsBenevolent() // T: __benevolent<int|string>
{
return \random_int(0, 1) === 1 ? 1 : 'key'; // V
}
/**
* @return int|string
*/
function returnsUnion(): int|string
{
return \random_int(0, 1) === 1 ? 1 : 'key';
}
/**
* @param __benevolent<int|string> $value
*/
function acceptsBenevolent($value): void // T: __benevolent<int|string>
{
}
// Both members of the inner union satisfy the parameter.
acceptsBenevolent(1); // V
acceptsBenevolent('key'); // V
// Inbound is still the union: a float is not an int or a string.
acceptsBenevolent(1.5); // E?: a float is not in the union, benevolent or not
// Honour is silence: the wrapper is accepted as either member.
takesInt(returnsBenevolent()); // Q?<phpstan> // Q?<phpstan-strict> // E?[noise]
takesString(returnsBenevolent()); // Q?<phpstan> // Q?<phpstan-strict> // E?[noise]
// Contrast: a sound union is rejected at the same calls. If these were also
// silent, the Q lines above would not be measuring benevolence. Noise so the
// contrast does not count as `__benevolent` enforcement.
takesInt(returnsUnion()); // E?[noise]: a plain int|string may be a string
takesString(returnsUnion()); // E?[noise]: a plain int|string may be an int