int + int is int; any float operand makes the result float.
Tools that expand both layers accept add(1, 2) as int and reject
add(1, 1.5) as int. Tools that fall back to the native int|float
reject the valid control as well.
References:
- PHPStan 1.6.0: nested ($a is int ? ($b is int ? int : float) : float) // E<noverify>: NoVerify reads $left in the condition as a class name
- PHPStan phpdoc-types: Conditional return types
- Psalm conditional_types.md: Nested conditionals
- Intelephense Type-System.md: Conditional Return Type| 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 | conditionals_nested_return.php:25: Saw unextractable annotation for comment '* @return ($left is int ? ($right is int ? int : float) : float)' [PhanUnextractableAnnotation]
|
| phpstan | 2.2.11PHPStan - PHP Static Analysis Tool 2.2.11 | Enforced reported Lv.5+ | conditionals_nested_return.php:48: Parameter #1 $value of function Conformance\Tests\ConditionalsNestedReturn\takesInt expects int, float given. [reported-from-level=5] conditionals_nested_return.php:49: Parameter #1 $value of function Conformance\Tests\ConditionalsNestedReturn\takesInt expects int, float given. [reported-from-level=5] With strict-rulesconditionals_nested_return.php:48: Parameter #1 $value of function Conformance\Tests\ConditionalsNestedReturn\takesInt expects int, float given. conditionals_nested_return.php:49: Parameter #1 $value of function Conformance\Tests\ConditionalsNestedReturn\takesInt expects int, float given.
|
| psalm | 6.16.1Psalm 6.16.1@f1f5de594dc76faf8784e02d3dc4716c91c6f6ac next: 7.0.0-beta19 |
Enforced | conditionals_nested_return.php:48: Argument 1 of Conformance\Tests\ConditionalsNestedReturn\takesInt expects int, but float provided [InvalidScalarArgument] conditionals_nested_return.php:49: Argument 1 of Conformance\Tests\ConditionalsNestedReturn\takesInt expects int, but float provided [InvalidScalarArgument]
|
| mago | 1.47.4mago 1.47.4 | Enforced | conditionals_nested_return.php:48: Possible argument type mismatch for argument #1 of `Conformance\Tests\ConditionalsNestedReturn\takesInt`: expected `int`, but possibly received `float`. [possibly-invalid-argument] conditionals_nested_return.php:49: Possible argument type mismatch for argument #1 of `Conformance\Tests\ConditionalsNestedReturn\takesInt`: expected `int`, but possibly received `float`. [possibly-invalid-argument]
|
| mir | 0.72.1mir 0.72.1 | Not enforced | No diagnostics reported.
|
| phpantom | 0.10.0phpantom_lsp 0.10.0 | Enforced | conditionals_nested_return.php:48: Argument 1 ($value) expects int, got float [type_mismatch_argument] conditionals_nested_return.php:49: Argument 1 ($value) expects int, got float [type_mismatch_argument]
|
| intelephense | 1.18.5intelephense 1.18.5 | Enforced | conditionals_nested_return.php:48: Expected type 'int'. Found 'float'. [P1006] conditionals_nested_return.php:49: Expected type 'int'. Found 'float'. [P1006]
|
| phpactor | 2026.07.22.0Phpactor 2026.07.22.0 | Not enforced | No diagnostics reported.
|
| phpy | 0.2.0phpy 0.2.0 | Not enforced | No diagnostics reported.
|
| qodana | 262.9437.196Qodana 262.9437.196 | Not enforced | No diagnostics reported.
|
| noverify | 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 | Partly enforced (1/3) | conditionals_nested_return.php:16: Class or interface named \Conformance\Tests\ConditionalsNestedReturn\$left does not exist [undefinedClass]
|
| steins | 0.1.6steins 0.1.6 (2026-08-25 revision c13b4c7) | Not enforced | No diagnostics reported.
|
| phpstan-strict | 2.2.11PHPStan - PHP Static Analysis Tool 2.2.11 | Enforced | conditionals_nested_return.php:48: Parameter #1 $value of function Conformance\Tests\ConditionalsNestedReturn\takesInt expects int, float given. conditionals_nested_return.php:49: Parameter #1 $value of function Conformance\Tests\ConditionalsNestedReturn\takesInt expects int, float given.
|
| psalm-next | 7.0.0-beta19Psalm 7.0.0-beta19@7e751c06a756fa64dc4c759c09fe4a173afcb433 | Enforced | conditionals_nested_return.php:48: Argument 1 of Conformance\Tests\ConditionalsNestedReturn\takesInt expects int, but float provided [InvalidScalarArgument] conditionals_nested_return.php:49: Argument 1 of Conformance\Tests\ConditionalsNestedReturn\takesInt expects int, but float provided [InvalidScalarArgument]
|
<?php
declare(strict_types=1);
namespace Conformance\Tests\ConditionalsNestedReturn;
/**
* Nested conditional return type.
*
* `int + int` is `int`; any float operand makes the result `float`.
* Tools that expand both layers accept `add(1, 2)` as `int` and reject
* `add(1, 1.5)` as `int`. Tools that fall back to the native `int|float`
* reject the valid control as well.
*
* References:
* - PHPStan 1.6.0: nested `($a is int ? ($b is int ? int : float) : float)` // E<noverify>: NoVerify reads $left in the condition as a class name
* - PHPStan phpdoc-types: Conditional return types
* - Psalm conditional_types.md: Nested conditionals
* - Intelephense Type-System.md: Conditional Return Type
*/
/**
* @param int|float $left
* @param int|float $right
* @return ($left is int ? ($right is int ? int : float) : float)
*/
function add(int|float $left, int|float $right): int|float // T: ($left is int ? ($right is int ? int : float) : float)
{
if (is_int($left) && is_int($right)) {
return $left + $right;
}
return 0.5;
}
function takesInt(int $value): void
{
}
function takesFloat(float $value): void
{
}
takesInt(add(1, 2)); // V: int + int is int
takesFloat(add(1, 1.5)); // V: int + float is float
takesFloat(add(1.5, 1)); // V: float + int is float
takesInt(add(1, 1.5)); // E?: int + float is float, not int
takesInt(add(1.5, 1)); // E?: float + int is float, not int