← All results

Transitive @mixin (depth ≥ 2)

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

Outer mixes in Middle, which mixes in Inner. Honouring the chain makes Outer::answer() the int from Inner. A tool that only walks one hop sees Middle with no answer() and routes the call through __call as mixed. Depth one is already measured in phpdoc_advanced_mixin. Source: vimeo/psalm#11868 (6.19.0), from Laravel empty query-builder contracts. References: - psalm#11868

Analyzer results

AnalyzerVersionResultDiagnostics
phan 6.0.10Phan 6.0.10 php-ast version 1.1.3 PHP version used to run Phan: 8.5.11 Recognized (no probes)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
phpstan 2.3.1PHPStan - PHP Static Analysis Tool 2.3.1 Enforced

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 2/2 of the expected violations reported
psalm 6.20.0Psalm 6.20.0@804f03c0c183d149fcd4b8c7c0c9b3fbf836d34c
next: 7.0.0-rc2
Enforced (pzoom≠)
pzoom (Psalm port)
phpdoc_advanced_mixin_transitive.php:73: Argument 1 of takesInt cannot be mixed, expecting int [MixedArgument]
phpdoc_advanced_mixin_transitive.php:73: Magic method Conformance\Tests\PhpdocAdvancedMixinTransitive\Outer::answer does not exist [UndefinedMagicMethod]
  • Recognition: spelling resolved
  • Enforcement: 2/2 of the expected violations reported
mago 1.57.0mago 1.57.0 Enforced

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 2/2 of the expected violations reported
mir 0.87.0mir 0.87.0 Recognized (no probes)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
phpantom 0.11.1phpantom_lsp 0.11.1 Recognized (no probes)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
intelephense 1.18.5intelephense 1.18.5 Not enforced
phpdoc_advanced_mixin_transitive.php:70: Expected type 'int'. Found 'mixed'. [P1006]
phpdoc_advanced_mixin_transitive.php:73: Expected type 'int'. Found 'mixed'. [P1006]
  • Recognition: spelling resolved
  • Enforcement: 0/2 of the expected violations reported
phpactor 2026.10.07.0Phpactor 2026.10.07.0 Recognized (no probes)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
phpy 1.0.19409phpy 1.0.19409 Recognized (no probes)
phpdoc_advanced_mixin_transitive.php:73: Method Outer::answer() is not defined. The call is passed to the magic Outer::__call() method.
  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
qodana 262.10968.76PhpStorm 262.10968.76 Recognized (no probes)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
noverify 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 Recognized (no probes)
phpdoc_advanced_mixin_transitive.php:37: param $arguments miss matched with phpdoc type <<list<mixed>>> [funcParamTypeMissMatch]
phpdoc_advanced_mixin_transitive.php:55: param $arguments miss matched with phpdoc type <<list<mixed>>> [funcParamTypeMissMatch]
  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
steins 0.1.8steins 0.1.8 (2026-09-21 revision 76306a9) Recognized (no probes)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: not measured — this test carries no rejection probes
phpstan-strict 2.3.1PHPStan - PHP Static Analysis Tool 2.3.1 Enforced

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 2/2 of the expected violations reported
psalm-next 7.0.0-rc2Psalm 7.0.0-rc2@20339e3fe5b8d03bcd8061030eb2a83ad50e5783 Enforced

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 2/2 of the expected violations reported

Source

<?php

declare(strict_types=1);

namespace Conformance\Tests\PhpdocAdvancedMixinTransitive;

/**
 * Transitive `@mixin` (depth ≥ 2).
 *
 * `Outer` mixes in `Middle`, which mixes in `Inner`. Honouring the chain makes
 * `Outer::answer()` the `int` from `Inner`. A tool that only walks one hop
 * sees `Middle` with no `answer()` and routes the call through `__call` as
 * `mixed`. Depth one is already measured in `phpdoc_advanced_mixin`.
 *
 * Source: vimeo/psalm#11868 (6.19.0), from Laravel empty query-builder contracts.
 *
 * References:
 * - https://github.com/vimeo/psalm/pull/11868
 */

final class Inner
{
    public function answer(): int
    {
        return 42;
    }
}

/**
 * @mixin Inner
 */
final class Middle // T: @mixin
{
    /**
     * @param list<mixed> $arguments
     */
    public function __call(string $name, array $arguments): mixed // E?[noise]: some tools flag the docblock/native array mismatch
    {
        if ($name === 'answer') {
            return (new Inner())->answer();
        }

        throw new \BadMethodCallException($name);
    }
}

/**
 * @mixin Middle
 */
final class Outer // T: @mixin
{
    /**
     * @param list<mixed> $arguments
     */
    public function __call(string $name, array $arguments): mixed // E?[noise]: some tools flag the docblock/native array mismatch
    {
        if ($name === 'answer') {
            return (new Middle())->answer();
        }

        throw new \BadMethodCallException($name);
    }
}

function takesInt(int $value): void
{
}

// Depth one, for contrast: Middle declares @mixin Inner.
takesInt((new Middle())->answer()); // Q?<phpstan> // Q?<phpstan-strict> // Q?<psalm> // Q?<pzoom> // Q?<mago> // Q?<intelephense> // E?[noise]

// Depth two: Outer only declares @mixin Middle.
takesInt((new Outer())->answer()); // Q?<phpstan> // Q?<phpstan-strict> // Q?<psalm> // Q?<pzoom> // Q?<mago> // Q?<intelephense> // E?[noise]