← All results

instanceof on a generic intersection keeps the type argument

Group: PHPDoc generics · Category: phpdoc · File: generics_instanceof_keeps_type_argument.php

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: - mago#2440

Analyzer results

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

  • Recognition: spelling resolved
  • Enforcement: 0/1 of the expected violations 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]
  • Recognition: spelling resolved
  • Enforcement: 1/1 of the expected violations reported — incidental, since values the type admits were also rejected on line(s) 67
Expectation diff
Line 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]
  • Recognition: spelling resolved
  • Enforcement: 0/1 of the expected violations reported
  • False positives: line(s) 34
Expectation diff
Line 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.

  • Recognition: spelling resolved
  • Enforcement: 0/1 of the expected violations 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]
  • Recognition: spelling resolved
  • Enforcement: 0/1 of the expected violations reported
  • False positives: line(s) 66
Expectation diff
Line 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.

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

Source

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