← All results

Collection<contravariant Dog> (call-site contravariance)

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

A PHPStan call-site variance spelling. Attaching contravariant to a type argument lets a function accept Collection<Animal> (or Collection<mixed>) where Collection<Dog> is written. Writes of Dog stay valid; reads are mixed, because the item type is not bounded from above. Declaration-site @template-contravariant is generics_template_contravariant. References: - PHPStan phpdoc-types: Generics (Collection<contravariant 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_contravariant.php:102: Saw possibly unextractable annotation for a fragment of comment '* @param Collection<contravariant Dog> $collection': after Collection, did not see an element name (will guess based on comment order) [PhanUnextractableAnnotationElementName]
phpdoc_advanced_phpstan_call_site_contravariant.php:102: Saw a token Phan may have failed to parse after '* @param Collection<contravariant Dog> $collection': after Collection, saw '<' [PhanUnextractableAnnotationSuffix]
phpdoc_advanced_phpstan_call_site_contravariant.php:109: Saw possibly unextractable annotation for a fragment of comment '* @param Collection<contravariant Dog> $collection': after Collection, did not see an element name (will guess based on comment order) [PhanUnextractableAnnotationElementName]
phpdoc_advanced_phpstan_call_site_contravariant.php:109: Saw a token Phan may have failed to parse after '* @param Collection<contravariant Dog> $collection': after Collection, saw '<' [PhanUnextractableAnnotationSuffix]
phpdoc_advanced_phpstan_call_site_contravariant.php:117: Saw possibly unextractable annotation for a fragment of comment '* @param Collection<contravariant Dog> $collection': after Collection, did not see an element name (will guess based on comment order) [PhanUnextractableAnnotationElementName]
phpdoc_advanced_phpstan_call_site_contravariant.php:117: Saw a token Phan may have failed to parse after '* @param Collection<contravariant Dog> $collection': after Collection, saw '<' [PhanUnextractableAnnotationSuffix]
phpdoc_advanced_phpstan_call_site_contravariant.php:148: Argument 1 ($collection) is animals() of type \Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection|\Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<\Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal> but \Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesInvariant() takes \Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<\Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog> defined at /Users/megurine/repo/php/php-typing-conformance/conformance/tests/phpdoc_advanced_phpstan_call_site_contravariant.php:127 [PhanTypeMismatchArgument]
  • Recognition: spelling not resolved — reported on declaration line(s) 102, 109, 117
  • 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_contravariant.php:121: Parameter #1 $dog of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesDog expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog, mixed given. [reported-from-level=9]
phpdoc_advanced_phpstan_call_site_contravariant.php:142: Parameter #1 $collection of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesContravariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<contravariant Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Cat> given. [reported-from-level=5]
phpdoc_advanced_phpstan_call_site_contravariant.php:148: Parameter #1 $collection of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesInvariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal> given. [reported-from-level=5]
With strict-rules
phpdoc_advanced_phpstan_call_site_contravariant.php:121: Parameter #1 $dog of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesDog expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog, mixed given.
phpdoc_advanced_phpstan_call_site_contravariant.php:142: Parameter #1 $collection of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesContravariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<contravariant Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Cat> given.
phpdoc_advanced_phpstan_call_site_contravariant.php:148: Parameter #1 $collection of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesInvariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal> 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
Partly enforced (1/2)
phpdoc_advanced_phpstan_call_site_contravariant.php:140: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesContravariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, but parent type Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal> provided [ArgumentTypeCoercion]
phpdoc_advanced_phpstan_call_site_contravariant.php:141: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesContravariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, but parent type Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<mixed> provided [MixedArgumentTypeCoercion]
phpdoc_advanced_phpstan_call_site_contravariant.php:142: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesContravariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, but Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Cat> provided [InvalidArgument]
phpdoc_advanced_phpstan_call_site_contravariant.php:143: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\writeContravariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, but parent type Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal> provided [ArgumentTypeCoercion]
phpdoc_advanced_phpstan_call_site_contravariant.php:148: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesInvariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, but parent type Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal> provided [ArgumentTypeCoercion]
  • Recognition: spelling resolved
  • Enforcement: 1/2 of the expected violations reported
Expectation diff
Line 140: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\takesContravariant expects Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Dog>, but parent type Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Animal> provided [ArgumentTypeCoercion]"]
Line 141: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\takesContravariant expects Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Dog>, but parent type Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<mixed> provided [MixedArgumentTypeCoercion]"]
Line 143: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\writeContravariant expects Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Dog>, but parent type Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Animal> provided [ArgumentTypeCoercion]"]

Notes: Ignores the `contravariant` keyword and treats the parameter as invariant Collection<Dog>. Collection<Animal> and Collection<mixed> are rejected on the valid calls (`ArgumentTypeCoercion`); Cat is rejected for the invariant reason, not because it is not a supertype under a projection.

mago 1.50.0mago 1.50.0 Enforced
phpdoc_advanced_phpstan_call_site_contravariant.php:121: Invalid argument type for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesDog`: expected `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog`, but found `mixed`. [mixed-argument]
phpdoc_advanced_phpstan_call_site_contravariant.php:142: Invalid argument type for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesContravariant`: expected `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>`, but found `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Cat>`. [invalid-argument]
phpdoc_advanced_phpstan_call_site_contravariant.php:148: Argument type mismatch for argument #1 of `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesInvariant`: expected `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>`, but provided type `Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal>` 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_contravariant.php:104: UndefinedDocblockClass: Docblock type 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\contravariant Dog' does not exist [MIR1505]
phpdoc_advanced_phpstan_call_site_contravariant.php:111: UndefinedDocblockClass: Docblock type 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\contravariant Dog' does not exist [MIR1505]
phpdoc_advanced_phpstan_call_site_contravariant.php:119: UndefinedDocblockClass: Docblock type 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\contravariant Dog' does not exist [MIR1505]
phpdoc_advanced_phpstan_call_site_contravariant.php:139: InvalidArgument: Argument $collection of takesContravariant() expects 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\contravariant Dog>', got 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>' [MIR0201]
phpdoc_advanced_phpstan_call_site_contravariant.php:140: InvalidArgument: Argument $collection of takesContravariant() expects 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\contravariant Dog>', got 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal>' [MIR0201]
phpdoc_advanced_phpstan_call_site_contravariant.php:142: InvalidArgument: Argument $collection of takesContravariant() expects 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\contravariant Dog>', got 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Cat>' [MIR0201]
phpdoc_advanced_phpstan_call_site_contravariant.php:143: InvalidArgument: Argument $collection of writeContravariant() expects 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\contravariant Dog>', got 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal>' [MIR0201]
phpdoc_advanced_phpstan_call_site_contravariant.php:144: InvalidArgument: Argument $collection of readContravariant() expects 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\contravariant Dog>', got 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>' [MIR0201]
phpdoc_advanced_phpstan_call_site_contravariant.php:148: InvalidArgument: Argument $collection of takesInvariant() expects 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>', got 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal>' [MIR0201]
  • Recognition: spelling not resolved — reported on declaration line(s) 104, 111, 119
  • Enforcement: 1/2 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 139: Expected valid value to be accepted, got ["InvalidArgument: Argument $collection of takesContravariant() expects 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\contravariant Dog>', got 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Dog>' [MIR0201]"]
Line 140: Expected valid value to be accepted, got ["InvalidArgument: Argument $collection of takesContravariant() expects 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\contravariant Dog>', got 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Animal>' [MIR0201]"]
Line 143: Expected valid value to be accepted, got ["InvalidArgument: Argument $collection of writeContravariant() expects 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\contravariant Dog>', got 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Animal>' [MIR0201]"]
Line 144: Expected valid value to be accepted, got ["InvalidArgument: Argument $collection of readContravariant() expects 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\contravariant Dog>', got 'Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Dog>' [MIR0201]"]

Notes: Reads `contravariant Dog` as a single class name (with the space). Collection<Dog> itself fails to match Collection<contravariant Dog>.

phpantom 0.10.0phpantom_lsp 0.10.0 Partly enforced (1/2)
phpdoc_advanced_phpstan_call_site_contravariant.php:142: Argument 1 ($collection) expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, got Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Cat> [type_mismatch_argument]
  • Recognition: spelling resolved
  • Enforcement: 1/2 of the expected violations reported

Notes: Rejects Collection<Cat> (which invariant Collection<Dog> also does) but accepts Collection<Animal> and Collection<mixed>, and does not treat get() as mixed. Subtyping in the contravariant direction without the read restriction.

intelephense 1.18.5intelephense 1.18.5 Unrecognized
phpdoc_advanced_phpstan_call_site_contravariant.php:102: PHPDoc undefined type 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\contravariant'. [P1133]
phpdoc_advanced_phpstan_call_site_contravariant.php:109: PHPDoc undefined type 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\contravariant'. [P1133]
phpdoc_advanced_phpstan_call_site_contravariant.php:117: PHPDoc undefined type 'Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\contravariant'. [P1133]
  • Recognition: spelling not resolved — reported on declaration line(s) 102, 109, 117
  • Enforcement: 0/2 of the expected violations reported — incidental, since the spelling was not resolved

Notes: Reports an undefined type `contravariant` and does not apply call-site contravariance.

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<contravariant Dog>: no undefined-type complaint on `contravariant`, and get() is not treated as mixed.

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_contravariant.php:43: param $item miss matched with phpdoc type <<T>> [funcParamTypeMissMatch]
phpdoc_advanced_phpstan_call_site_contravariant.php:104: param $collection miss matched with phpdoc type <<Collection<contravariant Dog>>> [funcParamTypeMissMatch]
phpdoc_advanced_phpstan_call_site_contravariant.php:111: param $collection miss matched with phpdoc type <<Collection<contravariant Dog>>> [funcParamTypeMissMatch]
phpdoc_advanced_phpstan_call_site_contravariant.php:119: param $collection miss matched with phpdoc type <<Collection<contravariant Dog>>> [funcParamTypeMissMatch]
phpdoc_advanced_phpstan_call_site_contravariant.php:127: param $collection miss matched with phpdoc type <<Collection<Dog>>> [funcParamTypeMissMatch]
phpdoc_advanced_phpstan_call_site_contravariant.php:134: param $collection miss matched with phpdoc type <<Collection<Dog>>> [funcParamTypeMissMatch]
  • Recognition: spelling not resolved — reported on declaration line(s) 104, 111, 119
  • Enforcement: 0/2 of the expected violations reported — incidental, since the spelling was not resolved
  • False positives: line(s) 43, 127, 134
Expectation diff
Line 43: Unexpected errors ["param $item miss matched with phpdoc type <<T>> [funcParamTypeMissMatch]"]
Line 127: Unexpected errors ["param $collection miss matched with phpdoc type <<Collection<Dog>>> [funcParamTypeMissMatch]"]
Line 134: Unexpected errors ["param $collection miss matched with phpdoc type <<Collection<Dog>>> [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_contravariant.php:121: Parameter #1 $dog of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesDog expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog, mixed given.
phpdoc_advanced_phpstan_call_site_contravariant.php:142: Parameter #1 $collection of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesContravariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<contravariant Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Cat> given.
phpdoc_advanced_phpstan_call_site_contravariant.php:148: Parameter #1 $collection of function Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesInvariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal> given.
  • Recognition: spelling resolved
  • Enforcement: 2/2 of the expected violations reported
psalm-next 7.0.0-beta22Psalm 7.0.0-beta22@de27e5724ee5997a1e3baee4d394ca6f4e46eba0 Partly enforced (1/2)
phpdoc_advanced_phpstan_call_site_contravariant.php:140: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesContravariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, but parent type Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal> provided [ArgumentTypeCoercion]
phpdoc_advanced_phpstan_call_site_contravariant.php:141: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesContravariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, but parent type Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<mixed> provided [MixedArgumentTypeCoercion]
phpdoc_advanced_phpstan_call_site_contravariant.php:142: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesContravariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, but Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Cat> provided [InvalidArgument]
phpdoc_advanced_phpstan_call_site_contravariant.php:143: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\writeContravariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, but parent type Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal> provided [ArgumentTypeCoercion]
phpdoc_advanced_phpstan_call_site_contravariant.php:148: Argument 1 of Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\takesInvariant expects Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Dog>, but parent type Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Collection<Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant\Animal> provided [ArgumentTypeCoercion]
  • Recognition: spelling resolved
  • Enforcement: 1/2 of the expected violations reported
Expectation diff
Line 140: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\takesContravariant expects Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Dog>, but parent type Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Animal> provided [ArgumentTypeCoercion]"]
Line 141: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\takesContravariant expects Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Dog>, but parent type Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<mixed> provided [MixedArgumentTypeCoercion]"]
Line 143: Expected valid value to be accepted, got ["Argument 1 of Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\writeContravariant expects Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Dog>, but parent type Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Collection<Conformance\\Tests\\PhpdocAdvancedPhpstanCallSiteContravariant\\Animal> provided [ArgumentTypeCoercion]"]

Notes: Ignores the `contravariant` keyword and treats the parameter as invariant Collection<Dog>. Collection<Animal> and Collection<mixed> are rejected on the valid calls (`ArgumentTypeCoercion`); Cat is rejected for the invariant reason, not because it is not a supertype under a projection.

Source

<?php

declare(strict_types=1);

namespace Conformance\Tests\PhpdocAdvancedPhpstanCallSiteContravariant;

/**
 * `Collection<contravariant Dog>` (call-site contravariance)
 *
 * A PHPStan call-site variance spelling. Attaching `contravariant` to a type
 * argument lets a function accept `Collection<Animal>` (or `Collection<mixed>`)
 * where `Collection<Dog>` is written. Writes of `Dog` stay valid; reads are
 * `mixed`, because the item type is not bounded from above.
 *
 * Declaration-site `@template-contravariant` is `generics_template_contravariant`.
 *
 * References:
 * - PHPStan phpdoc-types: Generics (`Collection<contravariant 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<Dog>
 */
function dogs(): Collection
{
    return new Collection(new Dog());
}

/**
 * @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);
}

function takesDog(Dog $dog): void
{
}

/**
 * @param Collection<contravariant Dog> $collection
 */
function takesContravariant(Collection $collection): void // T: Collection<contravariant Dog>
{
}

/**
 * @param Collection<contravariant Dog> $collection
 */
function writeContravariant(Collection $collection): void // T: Collection<contravariant Dog>
{
    $collection->add(new Dog()); // V: writes of Dog are allowed
}

/**
 * @param Collection<contravariant Dog> $collection
 */
function readContravariant(Collection $collection): void // T: Collection<contravariant Dog>
{
    takesDog($collection->get()); // E?: get() on a contravariant projection is mixed
}

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

/**
 * @param Collection<Dog> $collection
 */
function readInvariant(Collection $collection): void
{
    takesDog($collection->get()); // V: get() on Collection<Dog> is Dog
}

takesContravariant(dogs()); // V
takesContravariant(animals()); // V: Collection<Animal> satisfies Collection<contravariant Dog>
takesContravariant(mixeds()); // V: Collection<mixed> too
takesContravariant(cats()); // E?: Cat is not a supertype of Dog
writeContravariant(animals()); // V
readContravariant(dogs()); // V
readInvariant(dogs()); // V

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