← All results

Collection<covariant Animal> (call-site covariance)

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

A PHPStan call-site variance spelling. Attaching covariant to a type argument lets a function accept Collection<Cat> where Collection<Animal> is written, without making the class itself @template-covariant. The cost moves to that parameter: add() expects never, because a write would let a Dog into a collection of Cats. Declaration-site @template-covariant is generics_template_covariant. References: - PHPStan phpdoc-types: Generics (Collection<covariant Type>) - PHPStan "A guide to call-site generic variance"

Analyzer results

AnalyzerVersionResultDiagnostics
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_call_site_covariant.php:91: Saw possibly unextractable annotation for a fragment of comment '* @param Collection<covariant Animal> $animals': after Collection, did not see an element name (will guess based on comment order) [PhanUnextractableAnnotationElementName]
phpdoc_advanced_phpstan_call_site_covariant.php:91: Saw a token Phan may have failed to parse after '* @param Collection<covariant Animal> $animals': after Collection, saw '<' [PhanUnextractableAnnotationSuffix]
phpdoc_advanced_phpstan_call_site_covariant.php:98: Saw possibly unextractable annotation for a fragment of comment '* @param Collection<covariant Animal> $animals': after Collection, did not see an element name (will guess based on comment order) [PhanUnextractableAnnotationElementName]
phpdoc_advanced_phpstan_call_site_covariant.php:98: Saw a token Phan may have failed to parse after '* @param Collection<covariant Animal> $animals': after Collection, saw '<' [PhanUnextractableAnnotationSuffix]
  • Recognition: spelling not resolved — reported on declaration line(s) 91, 98
  • Enforcement: 0/2 of the expected violations reported — incidental, since the spelling was not resolved
phpstan 2.2.15PHPStan - PHP Static Analysis Tool 2.2.15 Enforced reported Lv.5+
phpdoc_advanced_phpstan_call_site_covariant.php:102: Parameter #1 $item of method Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<covariant Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>::add() expects never, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Dog given. [reported-from-level=5]
phpdoc_advanced_phpstan_call_site_covariant.php:122: Parameter #1 $animals of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesCovariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<covariant Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<mixed> given. [reported-from-level=9]
phpdoc_advanced_phpstan_call_site_covariant.php:127: Parameter #1 $animals of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesInvariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat> given. [reported-from-level=5]
With strict-rules
phpdoc_advanced_phpstan_call_site_covariant.php:102: Parameter #1 $item of method Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<covariant Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>::add() expects never, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Dog given.
phpdoc_advanced_phpstan_call_site_covariant.php:122: Parameter #1 $animals of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesCovariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<covariant Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<mixed> given.
phpdoc_advanced_phpstan_call_site_covariant.php:127: Parameter #1 $animals of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesInvariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat> given.
  • Recognition: spelling resolved
  • Enforcement: 2/2 of the expected violations reported
psalm 6.18.0Psalm 6.18.0@536dd6236b39a5115385b0307b42d6d1ad431a02
next: 7.0.0-beta22
Incidental (1/2)
phpdoc_advanced_phpstan_call_site_covariant.php:121: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesCovariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, but Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat> provided [InvalidArgument]
phpdoc_advanced_phpstan_call_site_covariant.php:122: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesCovariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, but parent type Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<mixed> provided [MixedArgumentTypeCoercion]
phpdoc_advanced_phpstan_call_site_covariant.php:123: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\writeCovariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, but Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat> provided [InvalidArgument]
phpdoc_advanced_phpstan_call_site_covariant.php:127: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesInvariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, but Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat> provided [InvalidArgument]
  • Recognition: spelling resolved
  • Enforcement: 1/2 of the expected violations reported — incidental, since values the type admits were also rejected on line(s) 121, 123
Expectation diff
Line 121: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\takesCovariant expects Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Animal>, but Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Cat> provided [InvalidArgument]"]
Line 123: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\writeCovariant expects Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Animal>, but Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Cat> provided [InvalidArgument]"]

Notes: Ignores the `covariant` keyword and treats the parameter as invariant Collection<Animal>. Collection<Cat> is rejected on the valid calls; the mixed rejection is the leftover invariant check, not call-site covariance.

mago 1.50.0mago 1.50.0 Enforced
phpdoc_advanced_phpstan_call_site_covariant.php:102: Possible argument type mismatch for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection::add`: expected `never`, but possibly received `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Dog`. [possibly-invalid-argument]
phpdoc_advanced_phpstan_call_site_covariant.php:122: Argument type mismatch for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesCovariant`: expected `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>`, but provided type `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<mixed>` is less specific. [less-specific-nested-argument-type]
phpdoc_advanced_phpstan_call_site_covariant.php:127: Argument type mismatch for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesInvariant`: expected `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>`, but provided type `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat>` is less specific. [less-specific-argument]
  • Recognition: spelling resolved
  • Enforcement: 2/2 of the expected violations reported
mir 0.78.0mir 0.78.0 Unrecognized
phpdoc_advanced_phpstan_call_site_covariant.php:93: UndefinedDocblockClass: Docblock type 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\covariant Animal' does not exist [MIR1505]
phpdoc_advanced_phpstan_call_site_covariant.php:100: UndefinedDocblockClass: Docblock type 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\covariant Animal' does not exist [MIR1505]
phpdoc_advanced_phpstan_call_site_covariant.php:120: InvalidArgument: Argument $animals of takesCovariant() expects 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\covariant Animal>', got 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>' [MIR0201]
phpdoc_advanced_phpstan_call_site_covariant.php:121: InvalidArgument: Argument $animals of takesCovariant() expects 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\covariant Animal>', got 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat>' [MIR0201]
phpdoc_advanced_phpstan_call_site_covariant.php:123: InvalidArgument: Argument $animals of writeCovariant() expects 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\covariant Animal>', got 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat>' [MIR0201]
phpdoc_advanced_phpstan_call_site_covariant.php:127: InvalidArgument: Argument $animals of takesInvariant() expects 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>', got 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat>' [MIR0201]
  • Recognition: spelling not resolved — reported on declaration line(s) 93, 100
  • Enforcement: 0/2 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 120: Expected valid value to be accepted, got ["InvalidArgument: Argument $animals of takesCovariant() expects 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\covariant Animal>', got 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Animal>' [MIR0201]"]
Line 121: Expected valid value to be accepted, got ["InvalidArgument: Argument $animals of takesCovariant() expects 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\covariant Animal>', got 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Cat>' [MIR0201]"]
Line 123: Expected valid value to be accepted, got ["InvalidArgument: Argument $animals of writeCovariant() expects 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\covariant Animal>', got 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Cat>' [MIR0201]"]

Notes: Reads `covariant Animal` as a single class name (with the space). Collection<Animal> and Collection<Cat> both fail to match Collection<covariant Animal>.

phpantom 0.10.0phpantom_lsp 0.10.0 Not enforced

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 0/2 of the expected violations reported
intelephense 1.18.5intelephense 1.18.5 Unrecognized
phpdoc_advanced_phpstan_call_site_covariant.php:91: PHPDoc undefined type 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\covariant'. [P1133]
phpdoc_advanced_phpstan_call_site_covariant.php:98: PHPDoc undefined type 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\covariant'. [P1133]
  • Recognition: spelling not resolved — reported on declaration line(s) 91, 98
  • Enforcement: 0/2 of the expected violations reported — incidental, since the spelling was not resolved

Notes: Reports an undefined type `covariant` and does not apply call-site covariance.

phpactor 2026.06.23.0Phpactor 2026.06.23.0 Not enforced

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 0/2 of the expected violations reported
phpy 1.0.19274phpy 1.0.19274 Not enforced

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 0/2 of the expected violations reported
qodana 262.9437.196Qodana 262.9437.196 Not enforced

No diagnostics reported.

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

Notes: Silent on Collection<covariant Animal>: no undefined-type complaint on `covariant`, Collection<Cat> is accepted, and add() is not restricted to never.

noverify 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 Unrecognized ⚠ 3 false positives
phpdoc_advanced_phpstan_call_site_covariant.php:44: param $item miss matched with phpdoc type <<T>> [funcParamTypeMissMatch]
phpdoc_advanced_phpstan_call_site_covariant.php:93: param $animals miss matched with phpdoc type <<Collection<covariant Animal>>> [funcParamTypeMissMatch]
phpdoc_advanced_phpstan_call_site_covariant.php:100: param $animals miss matched with phpdoc type <<Collection<covariant Animal>>> [funcParamTypeMissMatch]
phpdoc_advanced_phpstan_call_site_covariant.php:108: param $animals miss matched with phpdoc type <<Collection<Animal>>> [funcParamTypeMissMatch]
phpdoc_advanced_phpstan_call_site_covariant.php:116: param $animals miss matched with phpdoc type <<Collection<Animal>>> [funcParamTypeMissMatch]
  • Recognition: spelling not resolved — reported on declaration line(s) 93, 100
  • Enforcement: 0/2 of the expected violations reported — incidental, since the spelling was not resolved
  • False positives: line(s) 44, 108, 116
Expectation diff
Line 44: Unexpected errors ["param $item miss matched with phpdoc type <<T>> [funcParamTypeMissMatch]"]
Line 108: Unexpected errors ["param $animals miss matched with phpdoc type <<Collection<Animal>>> [funcParamTypeMissMatch]"]
Line 116: Unexpected errors ["param $animals miss matched with phpdoc type <<Collection<Animal>>> [funcParamTypeMissMatch]"]
steins 0.1.8steins 0.1.8 (2026-09-21 revision 76306a9) Not enforced

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 0/2 of the expected violations reported
phpstan-strict 2.2.15PHPStan - PHP Static Analysis Tool 2.2.15 Enforced
phpdoc_advanced_phpstan_call_site_covariant.php:102: Parameter #1 $item of method Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<covariant Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>::add() expects never, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Dog given.
phpdoc_advanced_phpstan_call_site_covariant.php:122: Parameter #1 $animals of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesCovariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<covariant Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<mixed> given.
phpdoc_advanced_phpstan_call_site_covariant.php:127: Parameter #1 $animals of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesInvariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat> given.
  • Recognition: spelling resolved
  • Enforcement: 2/2 of the expected violations reported
psalm-next 7.0.0-beta22Psalm 7.0.0-beta22@de27e5724ee5997a1e3baee4d394ca6f4e46eba0 Incidental (1/2)
phpdoc_advanced_phpstan_call_site_covariant.php:121: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesCovariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, but Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat> provided [InvalidArgument]
phpdoc_advanced_phpstan_call_site_covariant.php:122: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesCovariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, but parent type Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<mixed> provided [MixedArgumentTypeCoercion]
phpdoc_advanced_phpstan_call_site_covariant.php:123: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\writeCovariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, but Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat> provided [InvalidArgument]
phpdoc_advanced_phpstan_call_site_covariant.php:127: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\takesInvariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Animal>, but Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant\Cat> provided [InvalidArgument]
  • Recognition: spelling resolved
  • Enforcement: 1/2 of the expected violations reported — incidental, since values the type admits were also rejected on line(s) 121, 123
Expectation diff
Line 121: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\takesCovariant expects Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Animal>, but Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Cat> provided [InvalidArgument]"]
Line 123: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\writeCovariant expects Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Animal>, but Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteCovariant\\Cat> provided [InvalidArgument]"]

Notes: Ignores the `covariant` keyword and treats the parameter as invariant Collection<Animal>. Collection<Cat> is rejected on the valid calls; the mixed rejection is the leftover invariant check, not call-site covariance.

Source

<?php

declare(strict_types=1);

namespace Conformance\Tests\PhpdocAdvancedPhpstanCallSiteCovariant;

/**
 * `Collection<covariant Animal>` (call-site covariance)
 *
 * A PHPStan call-site variance spelling. Attaching `covariant` to a type
 * argument lets a function accept `Collection<Cat>` where `Collection<Animal>`
 * is written, without making the class itself `@template-covariant`. The cost
 * moves to that parameter: `add()` expects `never`, because a write would let
 * a Dog into a collection of Cats.
 *
 * Declaration-site `@template-covariant` is `generics_template_covariant`.
 *
 * References:
 * - PHPStan phpdoc-types: Generics (`Collection<covariant Type>`)
 * - PHPStan "A guide to call-site generic variance"
 */

class Animal
{
}

class Cat extends Animal
{
}

class Dog extends Animal
{
}

/**
 * @template T
 */
final class Collection
{
    /**
     * @param T $item
     */
    public function __construct(
        public mixed $item,
    ) {
    }

    /**
     * @param T $item
     */
    public function add($item): void
    {
        $this->item = $item;
    }

    /**
     * @return T
     */
    public function get(): mixed
    {
        return $this->item;
    }
}

/**
 * @return Collection<Animal>
 */
function animals(): Collection
{
    return new Collection(new Animal());
}

/**
 * @return Collection<Cat>
 */
function cats(): Collection
{
    return new Collection(new Cat());
}

/**
 * @param mixed $item
 * @return Collection<mixed>
 */
function mixeds(mixed $item = null): Collection
{
    return new Collection($item);
}

/**
 * @param Collection<covariant Animal> $animals
 */
function takesCovariant(Collection $animals): void // T: Collection<covariant Animal>
{
}

/**
 * @param Collection<covariant Animal> $animals
 */
function writeCovariant(Collection $animals): void // T: Collection<covariant Animal>
{
    $animals->add(new Dog()); // E?: add() on a covariant projection expects never
}

/**
 * @param Collection<Animal> $animals
 */
function writeInvariant(Collection $animals): void
{
    $animals->add(new Dog()); // V: the same write is valid on invariant Collection<Animal>
}

/**
 * @param Collection<Animal> $animals
 */
function takesInvariant(Collection $animals): void
{
}

takesCovariant(animals()); // V
takesCovariant(cats()); // V: Collection<Cat> satisfies Collection<covariant Animal>
takesCovariant(mixeds()); // E?: mixed is not a subtype of Animal
writeCovariant(cats()); // V
writeInvariant(animals()); // V

// Contrast: without the keyword, Collection is invariant.
takesInvariant(cats()); // E?[noise]: Collection<Cat> is not Collection<Animal>