← All results

@psalm-assert T $actual with a template taken from another argument

Group: Assertions and narrowing · Category: ecosystem · File: assertions_templated_assert.php

After assertSame(1, $value), $value is int (the template pinned by the first argument), so takesInt is valid and takesString is not. Non-templated @psalm-assert is phpdoc_advanced_vendor_prefixed_assert_psalm. References: - Psalm adding_assertions.md: @psalm-assert - vimeo/psalm 3.0.0 templated assertions

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 Partly enforced (2/4)
assertions_templated_assert.php:25: Template type T not used in return value of function/method assertSame() [PhanTemplateTypeNotUsedInFunctionReturn]
  • Recognition: spelling resolved
  • Enforcement: 2/4 of the expected violations reported
phpstan 2.3.5PHPStan - PHP Static Analysis Tool 2.3.5 Enforced reported Lv.4+
assertions_templated_assert.php:41: Call to function is_string() with 1 will always evaluate to false. [reported-from-level=4]
assertions_templated_assert.php:48: Call to function is_int() with 'x' will always evaluate to false. [reported-from-level=4]
With strict-rules
assertions_templated_assert.php:41: Call to function is_string() with 1 will always evaluate to false.
assertions_templated_assert.php:48: Call to function is_int() with 'x' will always evaluate to false.
  • Recognition: spelling resolved
  • Enforcement: 4/4 of the expected violations reported
psalm 6.20.9Psalm 6.20.9@f67170ac3c3b931f5088681e21d6c8babc601c08
next: 7.0.4
Enforced (pzoom≠)
assertions_templated_assert.php:41: Docblock-defined type 1 for $value is never string [DocblockTypeContradiction]
assertions_templated_assert.php:41: Docblock-defined type 1 for $value is never string [RedundantConditionGivenDocblockType]
assertions_templated_assert.php:48: Docblock-defined type 'x' for $value is never int [DocblockTypeContradiction]
assertions_templated_assert.php:48: Docblock-defined type 'x' for $value is never int [RedundantConditionGivenDocblockType]
pzoom (Psalm port)

No diagnostics from pzoom.

  • Recognition: spelling resolved
  • Enforcement: 4/4 of the expected violations reported
mago 1.59.0mago 1.59.0 Enforced
assertions_templated_assert.php:41: Impossible type assertion: `$value` of type `int(1)` can never be `string`. [impossible-type-comparison]
assertions_templated_assert.php:41: Redundant ternary operator: condition is always falsy. [impossible-condition]
assertions_templated_assert.php:48: Impossible type assertion: `$value` of type `string('x')` can never be `int`. [impossible-type-comparison]
assertions_templated_assert.php:48: Redundant ternary operator: condition is always falsy. [impossible-condition]
  • Recognition: spelling resolved
  • Enforcement: 4/4 of the expected violations reported
mir 0.91.0mir 0.91.0 Partly enforced (2/4)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 2/4 of the expected violations reported
phpantom 0.11.1phpantom_lsp 0.11.1 Not enforced
assertions_templated_assert.php:40: Argument 1 ($value) expects int, got object [type_mismatch_argument]
assertions_templated_assert.php:47: Argument 1 ($value) expects string, got object [type_mismatch_argument]
  • Recognition: spelling resolved
  • Enforcement: 0/4 of the expected violations reported
Expectation diff
Line 40: Expected silence (quiet probe), got ["Argument 1 ($value) expects int, got object [type_mismatch_argument]"]
Line 47: Expected silence (quiet probe), got ["Argument 1 ($value) expects string, got object [type_mismatch_argument]"]
intelephense 1.18.5intelephense 1.18.5 Partly enforced (2/4)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 2/4 of the expected violations reported
phpactor 2026.10.07.0Phpactor 2026.10.07.0 Partly enforced (2/4)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 2/4 of the expected violations reported
phpy 1.0.19490phpy 1.0.19490 Partly enforced (2/4)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 2/4 of the expected violations reported
qodana 262.11879.104PhpStorm 262.11879.104 Partly enforced (2/4) ⚠ 1 false positive
assertions_templated_assert.php:8: Undefined class 'T' [PhpUndefinedClassInspection]
  • Recognition: spelling resolved
  • Enforcement: 2/4 of the expected violations reported
  • False positives: line(s) 8
Expectation diff
Line 8: Unexpected errors ["Undefined class 'T' [PhpUndefinedClassInspection]"]
noverify 0.5.5NoVerify, version 0.5.5: built on: 2025.04.22 14:36:46 OS: r-kuchinskas arm64 Commit: 4774b82f8adc53fbef38f95e97af4b953b4d1529 Unrecognized
assertions_templated_assert.php:25: param $expected miss matched with phpdoc type <<T>> [funcParamTypeMissMatch]
assertions_templated_assert.php:40: not null safety call in function Conformance\Tests\AssertionsTemplatedAssert\takesInt signature of param value [notNullSafetyFunctionArgumentVariable]
assertions_templated_assert.php:47: not null safety call in function Conformance\Tests\AssertionsTemplatedAssert\takesString signature of param value [notNullSafetyFunctionArgumentVariable]
  • Recognition: spelling not resolved — reported on declaration line(s) 25
  • Enforcement: 0/4 of the expected violations reported — incidental, since the spelling was not resolved
Expectation diff
Line 40: Expected silence (quiet probe), got ["not null safety call in function Conformance\\Tests\\AssertionsTemplatedAssert\\takesInt signature of param value [notNullSafetyFunctionArgumentVariable]"]
Line 47: Expected silence (quiet probe), got ["not null safety call in function Conformance\\Tests\\AssertionsTemplatedAssert\\takesString signature of param value [notNullSafetyFunctionArgumentVariable]"]
steins 0.1.8steins 0.1.8 (2026-09-21 revision 76306a9) Partly enforced (2/4)

No diagnostics reported.

  • Recognition: spelling resolved
  • Enforcement: 2/4 of the expected violations reported
phpstan-strict 2.3.5PHPStan - PHP Static Analysis Tool 2.3.5 Enforced
assertions_templated_assert.php:41: Call to function is_string() with 1 will always evaluate to false.
assertions_templated_assert.php:48: Call to function is_int() with 'x' will always evaluate to false.
  • Recognition: spelling resolved
  • Enforcement: 4/4 of the expected violations reported
psalm-next 7.0.4Psalm 7.0.4@fa5b1f6e665cb743838ffd865b8fc96207f9409f Enforced
assertions_templated_assert.php:41: Docblock-defined type 1 for $value is never string [DocblockTypeContradiction]
assertions_templated_assert.php:41: Docblock-defined type 1 for $value is never string [RedundantConditionGivenDocblockType]
assertions_templated_assert.php:48: Docblock-defined type 'x' for $value is never int [DocblockTypeContradiction]
assertions_templated_assert.php:48: Docblock-defined type 'x' for $value is never int [RedundantConditionGivenDocblockType]
  • Recognition: spelling resolved
  • Enforcement: 4/4 of the expected violations reported

Source

<?php

declare(strict_types=1);

namespace Conformance\Tests\AssertionsTemplatedAssert;

/**
 * `@psalm-assert T $actual` with a template taken from another argument.
 *
 * After `assertSame(1, $value)`, `$value` is `int` (the template pinned by
 * the first argument), so `takesInt` is valid and `takesString` is not.
 * Non-templated `@psalm-assert` is `phpdoc_advanced_vendor_prefixed_assert_psalm`.
 *
 * References:
 * - Psalm adding_assertions.md: @psalm-assert
 * - vimeo/psalm 3.0.0 templated assertions
 */

/**
 * @template T
 * @param T $expected
 * @psalm-assert T $actual
 * @phpstan-assert T $actual
 */
function assertSame(mixed $expected, mixed $actual): void // T: @psalm-assert
{
}

function takesInt(int $value): void
{
}

function takesString(string $value): void
{
}

function fromIntLiteral(mixed $value): void
{
    assertSame(1, $value);
    takesInt($value); // Q: the template assertion pins $value to int
    echo \is_string($value) ? 'string' : 'int'; // E?: after assert int, is_string() is always false
}

function fromStringLiteral(mixed $value): void
{
    assertSame('x', $value);
    takesString($value); // Q: the template assertion pins $value to string
    echo \is_int($value) ? 'int' : 'string'; // E?: after assert string, is_int() is always false
}