diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php index 7c2938f3014..2b00fde7a05 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php @@ -1292,9 +1292,14 @@ private static function getInstanceOfAssertions( ), ); } elseif ($atomic_type instanceof TClassString && $atomic_type->as !== 'object') { - $literal_class_strings[] = new IsType( - $atomic_type->as_type ?: new TNamedObject($atomic_type->as), - ); + $as_type = $atomic_type->as_type ?: new TNamedObject($atomic_type->as); + // an interface class-string can hold any implementor, so this is is_a, not a + // fixed type, and must not read as redundant (#11076) + if ($source->getCodebase()->interfaceExists($atomic_type->as)) { + $literal_class_strings[] = new IsAClass($as_type, false); + } else { + $literal_class_strings[] = new IsType($as_type); + } } } diff --git a/tests/TypeReconciliation/RedundantConditionTest.php b/tests/TypeReconciliation/RedundantConditionTest.php index 83c0e529a5e..929ae1875ef 100644 --- a/tests/TypeReconciliation/RedundantConditionTest.php +++ b/tests/TypeReconciliation/RedundantConditionTest.php @@ -983,6 +983,18 @@ function f(array $p) : void { assert(!!$p); }', ], + 'instanceofClassStringVarIsNotRedundant' => [ + 'code' => ' $class + */ + function test(Traversable $input, string $class): bool { + if ($input instanceof $class) { + return true; + } + return false; + }', + ], ]; }