instanceof on a generic intersection keeps the type argument$value instanceof Box on Container<string>&Box, $value is
still a Box whose T is string. A tool that keeps Box but drops
T types $value->value as mixed and stays silent on takesInt.
Source: carthage-software/mago#2440 (1.55.0).
References:
- mago#2440| Analyzer | Version | Result | Diagnostics |
|---|---|---|---|
| phan | 6.0.11Phan 6.0.11 php-ast version 1.1.3 PHP version used to run Phan: 8.5.11 | Unrecognized ⚠ 1 false positive | generics_instanceof_keeps_type_argument.php:34: Class \Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box must substitute all 1 template parameters when inheriting \Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Container (found 0) defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/generics_instanceof_keeps_type_argument.php:23 (use @extends or @inherit) [PhanGenericMissingParameters] generics_instanceof_keeps_type_argument.php:64: Intersection type \Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box&\Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Container<string> contains part \Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Container<string> which cannot cast to the declared type \Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box [PhanImpossibleIntersectionType]
Expectation diffLine 34: Unexpected errors ["Class \\Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box must substitute all 1 template parameters when inheriting \\Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Container (found 0) defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/generics_instanceof_keeps_type_argument.php:23 (use @extends or @inherit) [PhanGenericMissingParameters]"] |
| phpstan | 2.3.5PHPStan - PHP Static Analysis Tool 2.3.5 | Unrecognized | generics_instanceof_keeps_type_argument.php:34: Class Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box implements generic interface Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Container but does not specify its types: T [reported-from-level=6] generics_instanceof_keeps_type_argument.php:64: Function Conformance\Tests\GenericsInstanceofKeepsTypeArgument\afterInstanceofBox() has parameter $value with generic class Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box but does not specify its types: T [reported-from-level=6] generics_instanceof_keeps_type_argument.php:66: Instanceof between Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box and Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box will always evaluate to true. [reported-from-level=4] generics_instanceof_keeps_type_argument.php:67: Parameter #1 $value of function Conformance\Tests\GenericsInstanceofKeepsTypeArgument\takesString expects string, mixed given. [reported-from-level=9] generics_instanceof_keeps_type_argument.php:68: Parameter #1 $value of function Conformance\Tests\GenericsInstanceofKeepsTypeArgument\takesInt expects int, mixed given. [reported-from-level=9] With strict-rulesgenerics_instanceof_keeps_type_argument.php:34: Class Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box implements generic interface Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Container but does not specify its types: T generics_instanceof_keeps_type_argument.php:64: Function Conformance\Tests\GenericsInstanceofKeepsTypeArgument\afterInstanceofBox() has parameter $value with generic class Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box but does not specify its types: T generics_instanceof_keeps_type_argument.php:66: Instanceof between Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box and Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box will always evaluate to true. generics_instanceof_keeps_type_argument.php:67: Parameter #1 $value of function Conformance\Tests\GenericsInstanceofKeepsTypeArgument\takesString expects string, mixed given. generics_instanceof_keeps_type_argument.php:68: Parameter #1 $value of function Conformance\Tests\GenericsInstanceofKeepsTypeArgument\takesInt expects int, mixed given.
Expectation diffLine 67: Expected valid value to be accepted, got ["Parameter #1 $value of function Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\takesString expects string, mixed given. [reported-from-level=9]"] Line 34: Unexpected errors ["Class Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box implements generic interface Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Container but does not specify its types: T [reported-from-level=6]"] Line 66: Unexpected errors ["Instanceof between Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box and Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box will always evaluate to true. [reported-from-level=4]"] |
| psalm | 6.20.9Psalm 6.20.9@f67170ac3c3b931f5088681e21d6c8babc601c08 next: 7.0.4 |
Enforced (pzoom≠) | generics_instanceof_keeps_type_argument.php:34: Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box has missing template params when extending Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Container, expecting 1 [MissingTemplateParam] generics_instanceof_keeps_type_argument.php:47: Method Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box::get should have the "Override" attribute [MissingOverrideAttribute] generics_instanceof_keeps_type_argument.php:66: Docblock-defined type Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Container<string>&Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box for $value is always Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box [RedundantConditionGivenDocblockType] generics_instanceof_keeps_type_argument.php:68: Argument 1 of Conformance\Tests\GenericsInstanceofKeepsTypeArgument\takesInt expects int, but string provided [InvalidScalarArgument] pzoom (Psalm port)generics_instanceof_keeps_type_argument.php:34: Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box has missing template params when extending Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Container [MissingTemplateParam] generics_instanceof_keeps_type_argument.php:67: Argument 1 of takesString cannot be mixed, expecting string [MixedArgument] generics_instanceof_keeps_type_argument.php:68: Argument 1 of takesInt cannot be mixed, expecting int [MixedArgument]
Expectation diffLine 34: Unexpected errors ["Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box has missing template params when extending Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Container, expecting 1 [MissingTemplateParam]"] Line 47: Unexpected errors ["Method Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box::get should have the \"Override\" attribute [MissingOverrideAttribute]"] Line 66: Unexpected errors ["Docblock-defined type Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Container<string>&Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box for $value is always Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box [RedundantConditionGivenDocblockType]"] |
| mago | 1.59.0mago 1.59.0 | Incidental (1/1) ⚠ 2 false positives | generics_instanceof_keeps_type_argument.php:34: Too few template arguments for `Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Container`: expected at least 1, but found 0. [missing-template-parameter]
generics_instanceof_keeps_type_argument.php:66: This condition (type `true`) will always evaluate to true. [redundant-condition]
generics_instanceof_keeps_type_argument.php:67: Argument type mismatch for argument #1 of `Conformance\Tests\GenericsInstanceofKeepsTypeArgument\takesString`: expected `string`, but provided type `('T.conformance\tests\genericsinstanceofkeepstypeargument\box extends mixed)` is less specific. [less-specific-nested-argument-type]
generics_instanceof_keeps_type_argument.php:68: Argument type mismatch for argument #1 of `Conformance\Tests\GenericsInstanceofKeepsTypeArgument\takesInt`: expected `int`, but provided type `('T.conformance\tests\genericsinstanceofkeepstypeargument\box extends mixed)` is less specific. [less-specific-nested-argument-type]
Expectation diffLine 67: Expected valid value to be accepted, got ["Argument type mismatch for argument #1 of `Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\takesString`: expected `string`, but provided type `('T.conformance\\tests\\genericsinstanceofkeepstypeargument\\box extends mixed)` is less specific. [less-specific-nested-argument-type]"]
Line 34: Unexpected errors ["Too few template arguments for `Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Container`: expected at least 1, but found 0. [missing-template-parameter]"]
Line 66: Unexpected errors ["This condition (type `true`) will always evaluate to true. [redundant-condition]"] |
| mir | 0.91.0mir 0.91.0 | Enforced ⚠ 1 false positive | generics_instanceof_keeps_type_argument.php:66: RedundantCondition: Condition is always true, so the check is redundant [MIR0400] generics_instanceof_keeps_type_argument.php:67: MixedArgument: Argument $value of takesString() is mixed [MIR1200] generics_instanceof_keeps_type_argument.php:68: MixedArgument: Argument $value of takesInt() is mixed [MIR1200]
Expectation diffLine 67: Expected valid value to be accepted, got ["MixedArgument: Argument $value of takesString() is mixed [MIR1200]"] Line 66: Unexpected errors ["RedundantCondition: Condition is always true, so the check is redundant [MIR0400]"] |
| phpantom | 0.11.1phpantom_lsp 0.11.1 | Not enforced | No diagnostics reported.
|
| intelephense | 1.18.5intelephense 1.18.5 | Incidental (1/1) | generics_instanceof_keeps_type_argument.php:67: Expected type 'string'. Found 'mixed'. [P1006] generics_instanceof_keeps_type_argument.php:68: Expected type 'int'. Found 'mixed'. [P1006]
Expectation diffLine 67: Expected valid value to be accepted, got ["Expected type 'string'. Found 'mixed'. [P1006]"] |
| phpactor | 2026.10.07.0Phpactor 2026.10.07.0 | Not enforced ⚠ 1 false positive | generics_instanceof_keeps_type_argument.php:34: Missing generic tag `@implements Container<mixed>` [docblock_missing_class_generic]
Expectation diffLine 34: Unexpected errors ["Missing generic tag `@implements Container<mixed>` [docblock_missing_class_generic]"] |
| phpy | 1.0.19490phpy 1.0.19490 | Not enforced | No diagnostics reported.
|
| qodana | 262.11879.104PhpStorm 262.11879.104 | Not enforced ⚠ 1 false positive | generics_instanceof_keeps_type_argument.php:66: Condition is always 'true' because 'Container&Box $value' is evaluated at this point [PhpConditionAlreadyCheckedInspection]
Expectation diffLine 66: Unexpected errors ["Condition is always 'true' because 'Container&Box $value' is evaluated at this point [PhpConditionAlreadyCheckedInspection]"] |
| noverify | 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 | Not enforced | No diagnostics reported.
|
| steins | 0.1.8steins 0.1.8 (2026-09-21 revision 76306a9) | Unrecognized | generics_instanceof_keeps_type_argument.php:64: parameter $value of afterInstanceofBox() names the generic class Box without type arguments — it declares `@template T` [untyped.generics]
|
| phpstan-strict | 2.3.5PHPStan - PHP Static Analysis Tool 2.3.5 | Unrecognized ⚠ 2 false positives | generics_instanceof_keeps_type_argument.php:34: Class Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box implements generic interface Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Container but does not specify its types: T generics_instanceof_keeps_type_argument.php:64: Function Conformance\Tests\GenericsInstanceofKeepsTypeArgument\afterInstanceofBox() has parameter $value with generic class Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box but does not specify its types: T generics_instanceof_keeps_type_argument.php:66: Instanceof between Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box and Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box will always evaluate to true. generics_instanceof_keeps_type_argument.php:67: Parameter #1 $value of function Conformance\Tests\GenericsInstanceofKeepsTypeArgument\takesString expects string, mixed given. generics_instanceof_keeps_type_argument.php:68: Parameter #1 $value of function Conformance\Tests\GenericsInstanceofKeepsTypeArgument\takesInt expects int, mixed given.
Expectation diffLine 67: Expected valid value to be accepted, got ["Parameter #1 $value of function Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\takesString expects string, mixed given."] Line 34: Unexpected errors ["Class Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box implements generic interface Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Container but does not specify its types: T"] Line 66: Unexpected errors ["Instanceof between Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box and Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box will always evaluate to true."] |
| psalm-next | 7.0.4Psalm 7.0.4@fa5b1f6e665cb743838ffd865b8fc96207f9409f | Enforced ⚠ 3 false positives | generics_instanceof_keeps_type_argument.php:34: Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box has missing template params when extending Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Container, expecting 1 [MissingTemplateParam] generics_instanceof_keeps_type_argument.php:47: Method Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box::get should have the "Override" attribute [MissingOverrideAttribute] generics_instanceof_keeps_type_argument.php:66: Docblock-defined type Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Container<string>&Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box for $value is always Conformance\Tests\GenericsInstanceofKeepsTypeArgument\Box [RedundantConditionGivenDocblockType] generics_instanceof_keeps_type_argument.php:68: Argument 1 of Conformance\Tests\GenericsInstanceofKeepsTypeArgument\takesInt expects int, but string provided [InvalidScalarArgument]
Expectation diffLine 34: Unexpected errors ["Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box has missing template params when extending Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Container, expecting 1 [MissingTemplateParam]"] Line 47: Unexpected errors ["Method Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box::get should have the \"Override\" attribute [MissingOverrideAttribute]"] Line 66: Unexpected errors ["Docblock-defined type Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Container<string>&Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box for $value is always Conformance\\Tests\\GenericsInstanceofKeepsTypeArgument\\Box [RedundantConditionGivenDocblockType]"] |
<?php
declare(strict_types=1);
namespace Conformance\Tests\GenericsInstanceofKeepsTypeArgument;
/**
* `instanceof` on a generic intersection keeps the type argument.
*
* After `$value instanceof Box` on `Container<string>&Box`, `$value` is
* still a `Box` whose `T` is `string`. A tool that keeps `Box` but drops
* `T` types `$value->value` as `mixed` and stays silent on `takesInt`.
*
* Source: carthage-software/mago#2440 (1.55.0).
*
* References:
* - https://github.com/carthage-software/mago/pull/2440
*/
/**
* @template T
*/
interface Container
{
/**
* @return T
*/
public function get(): mixed;
}
/**
* @template T
*/
final class Box implements Container
{
/**
* @param T $value
*/
public function __construct(
public mixed $value,
) {
}
/**
* @return T
*/
public function get(): mixed
{
return $this->value;
}
}
function takesString(string $value): void
{
}
function takesInt(int $value): void
{
}
/**
* @param Container<string>&Box $value
*/
function afterInstanceofBox(Container&Box $value): void // T: Container<string>&Box
{
if ($value instanceof Box) {
takesString($value->value); // V: T is still string
takesInt($value->value); // E?: string is not int
}
}