From dc76f12744985b2e12729b112ac67b1cd5a08b27 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vojt=C4=9Bch=20Dobe=C5=A1?= <me@vojtechdobes.com>
Date: Mon, 23 Jun 2025 17:45:11 +0200
Subject: [PATCH 1/2] Test quality of life checks for assertSuperType

---
 .../Testing/TypeInferenceTestCaseTest.php     | 21 +++++++++++++++++++
 .../assert-super-type-case-insensitive.php    |  8 +++++++
 .../assert-super-type-missing-namespace.php   |  8 +++++++
 .../assert-super-type-wrong-namespace.php     | 10 +++++++++
 4 files changed, 47 insertions(+)
 create mode 100644 tests/PHPStan/Testing/data/assert-super-type-case-insensitive.php
 create mode 100644 tests/PHPStan/Testing/data/assert-super-type-missing-namespace.php
 create mode 100644 tests/PHPStan/Testing/data/assert-super-type-wrong-namespace.php

diff --git a/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php b/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php
index cce8bd958a..35856fef8b 100644
--- a/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php
+++ b/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php
@@ -37,6 +37,13 @@ public static function dataFileAssertionFailedErrors(): iterable
 				$fileHelper->normalizePath('tests/PHPStan/Testing/data/assert-type-missing-namespace.php'),
 			),
 		];
+		yield [
+			__DIR__ . '/data/assert-super-type-missing-namespace.php',
+			sprintf(
+				'Missing use statement for assertSuperType() in %s on line 6.',
+				$fileHelper->normalizePath('tests/PHPStan/Testing/data/assert-super-type-missing-namespace.php'),
+			),
+		];
 		yield [
 			__DIR__ . '/data/assert-certainty-wrong-namespace.php',
 			sprintf(
@@ -58,6 +65,13 @@ public static function dataFileAssertionFailedErrors(): iterable
 				$fileHelper->normalizePath('tests/PHPStan/Testing/data/assert-type-wrong-namespace.php'),
 			),
 		];
+		yield [
+			__DIR__ . '/data/assert-super-type-wrong-namespace.php',
+			sprintf(
+				'Function PHPStan\Testing\assertSuperType imported with wrong namespace SomeWrong\Namespace\assertSuperType called in %s on line 8.',
+				$fileHelper->normalizePath('tests/PHPStan/Testing/data/assert-super-type-wrong-namespace.php'),
+			),
+		];
 		yield [
 			__DIR__ . '/data/assert-certainty-case-insensitive.php',
 			sprintf(
@@ -79,6 +93,13 @@ public static function dataFileAssertionFailedErrors(): iterable
 				$fileHelper->normalizePath('tests/PHPStan/Testing/data/assert-type-case-insensitive.php'),
 			),
 		];
+		yield [
+			__DIR__ . '/data/assert-super-type-case-insensitive.php',
+			sprintf(
+				'Missing use statement for assertSuperTYPe() in %s on line 6.',
+				$fileHelper->normalizePath('tests/PHPStan/Testing/data/assert-super-type-case-insensitive.php'),
+			),
+		];
 	}
 
 	#[DataProvider('dataFileAssertionFailedErrors')]
diff --git a/tests/PHPStan/Testing/data/assert-super-type-case-insensitive.php b/tests/PHPStan/Testing/data/assert-super-type-case-insensitive.php
new file mode 100644
index 0000000000..40137f9dad
--- /dev/null
+++ b/tests/PHPStan/Testing/data/assert-super-type-case-insensitive.php
@@ -0,0 +1,8 @@
+<?php // lint >= 8.0
+
+namespace MissingTypeCaseSensitive;
+
+function doFoo(string $s) {
+	assertSuperTYPe('string', $s);
+}
+
diff --git a/tests/PHPStan/Testing/data/assert-super-type-missing-namespace.php b/tests/PHPStan/Testing/data/assert-super-type-missing-namespace.php
new file mode 100644
index 0000000000..0f7ed172e8
--- /dev/null
+++ b/tests/PHPStan/Testing/data/assert-super-type-missing-namespace.php
@@ -0,0 +1,8 @@
+<?php // lint >= 8.0
+
+namespace MissingAssertTypeNamespace;
+
+function doFoo(string $s) {
+	assertSuperType('string', $s);
+}
+
diff --git a/tests/PHPStan/Testing/data/assert-super-type-wrong-namespace.php b/tests/PHPStan/Testing/data/assert-super-type-wrong-namespace.php
new file mode 100644
index 0000000000..6a6afcc511
--- /dev/null
+++ b/tests/PHPStan/Testing/data/assert-super-type-wrong-namespace.php
@@ -0,0 +1,10 @@
+<?php // lint >= 8.0
+
+namespace WrongAssertTypeNamespace;
+
+use function SomeWrong\Namespace\assertSuperType;
+
+function doFoo(string $s) {
+	assertSuperType('string', $s);
+}
+

From 693bb5227cc1b156d985b3709860cf1e3c440aba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vojt=C4=9Bch=20Dobe=C5=A1?= <me@vojtechdobes.com>
Date: Mon, 23 Jun 2025 17:45:30 +0200
Subject: [PATCH 2/2] Fix assertSuperType logic

---
 phpstan-baseline.neon                         |  4 ++--
 src/Testing/TypeInferenceTestCase.php         | 12 +++++++---
 .../Testing/TypeInferenceTestCaseTest.php     | 22 +++++++++++++++++++
 .../Testing/data/assert-super-type-failed.php |  9 ++++++++
 .../Testing/data/assert-super-type.php        |  8 +++++++
 5 files changed, 50 insertions(+), 5 deletions(-)
 create mode 100644 tests/PHPStan/Testing/data/assert-super-type-failed.php
 create mode 100644 tests/PHPStan/Testing/data/assert-super-type.php

diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 94d21ed70e..e80f928a45 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -780,7 +780,7 @@ parameters:
 		-
 			message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#'
 			identifier: phpstanApi.instanceofType
-			count: 3
+			count: 2
 			path: src/Testing/TypeInferenceTestCase.php
 
 		-
@@ -2022,7 +2022,7 @@ parameters:
 		-
 			message: '#^Access to constant on internal class PHPUnit\\Framework\\AssertionFailedError\.$#'
 			identifier: classConstant.internalClass
-			count: 1
+			count: 2
 			path: tests/PHPStan/Testing/TypeInferenceTestCaseTest.php
 
 		-
diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php
index 1793d7b1ec..227ab22fe1 100644
--- a/src/Testing/TypeInferenceTestCase.php
+++ b/src/Testing/TypeInferenceTestCase.php
@@ -17,6 +17,7 @@
 use PHPStan\Php\PhpVersion;
 use PHPStan\PhpDoc\PhpDocInheritanceResolver;
 use PHPStan\PhpDoc\StubPhpDocProvider;
+use PHPStan\PhpDoc\TypeStringResolver;
 use PHPStan\Reflection\AttributeReflectionFactory;
 use PHPStan\Reflection\Deprecation\DeprecationProvider;
 use PHPStan\Reflection\InitializerExprTypeResolver;
@@ -203,12 +204,13 @@ public static function gatherAssertTypes(string $file): array
 
 		$relativePathHelper = new SystemAgnosticSimpleRelativePathHelper($fileHelper);
 		$reflectionProvider = self::getContainer()->getByType(ReflectionProvider::class);
+		$typeStringResolver = self::getContainer()->getByType(TypeStringResolver::class);
 
 		$file = $fileHelper->normalizePath($file);
 
 		$asserts = [];
 		$delayedErrors = [];
-		self::processFile($file, static function (Node $node, Scope $scope) use (&$asserts, &$delayedErrors, $file, $relativePathHelper, $reflectionProvider): void {
+		self::processFile($file, static function (Node $node, Scope $scope) use (&$asserts, &$delayedErrors, $file, $relativePathHelper, $reflectionProvider, $typeStringResolver): void {
 			if ($node instanceof InClassNode) {
 				if (!$reflectionProvider->hasClass($node->getClassReflection()->getName())) {
 					$delayedErrors[] = sprintf(
@@ -270,7 +272,8 @@ public static function gatherAssertTypes(string $file): array
 				$assert = ['type', $file, $expectedType->getValue(), $actualType->describe(VerbosityLevel::precise()), $node->getStartLine()];
 			} elseif ($functionName === 'PHPStan\\Testing\\assertSuperType') {
 				$expectedType = $scope->getType($node->getArgs()[0]->value);
-				if (!$expectedType instanceof ConstantScalarType) {
+				$expectedTypeStrings = $expectedType->getConstantStrings();
+				if (count($expectedTypeStrings) !== 1) {
 					self::fail(sprintf(
 						'Expected super type must be a literal string, %s given in %s on line %d.',
 						$expectedType->describe(VerbosityLevel::precise()),
@@ -278,8 +281,11 @@ public static function gatherAssertTypes(string $file): array
 						$node->getStartLine(),
 					));
 				}
+
 				$actualType = $scope->getType($node->getArgs()[1]->value);
-				$assert = ['superType', $file, $expectedType->getValue(), $actualType->describe(VerbosityLevel::precise()), $expectedType->isSuperTypeOf($actualType)->yes(), $node->getStartLine()];
+				$isCorrect = $typeStringResolver->resolve($expectedTypeStrings[0]->getValue())->isSuperTypeOf($actualType)->yes();
+
+				$assert = ['superType', $file, $expectedTypeStrings[0]->getValue(), $actualType->describe(VerbosityLevel::precise()), $isCorrect, $node->getStartLine()];
 			} elseif ($functionName === 'PHPStan\\Testing\\assertVariableCertainty') {
 				$certainty = $node->getArgs()[0]->value;
 				if (!$certainty instanceof StaticCall) {
diff --git a/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php b/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php
index 35856fef8b..054094eebb 100644
--- a/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php
+++ b/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php
@@ -121,6 +121,28 @@ public function testVariableOrOffsetDescription(): void
 		$this->assertSame("offset 'email'", $offsetAssert[4]);
 	}
 
+	public function testSuperType(): void
+	{
+		foreach (self::gatherAssertTypes(__DIR__ . '/data/assert-super-type.php') as $data) {
+			$this->assertFileAsserts(...$data);
+		}
+	}
+
+	public static function dataSuperTypeFailed(): array
+	{
+		return self::gatherAssertTypes(__DIR__ . '/data/assert-super-type-failed.php');
+	}
+
+	/**
+	 * @param mixed ...$args
+	 */
+	#[DataProvider('dataSuperTypeFailed')]
+	public function testSuperTypeFailed(...$args): void
+	{
+		$this->expectException(AssertionFailedError::class);
+		$this->assertFileAsserts(...$args);
+	}
+
 	public function testNonexistentClassInAnalysedFile(): void
 	{
 		foreach (self::gatherAssertTypes(__DIR__ . '/../../notAutoloaded/nonexistentClasses.php') as $data) {
diff --git a/tests/PHPStan/Testing/data/assert-super-type-failed.php b/tests/PHPStan/Testing/data/assert-super-type-failed.php
new file mode 100644
index 0000000000..6c6f8dd5e6
--- /dev/null
+++ b/tests/PHPStan/Testing/data/assert-super-type-failed.php
@@ -0,0 +1,9 @@
+<?php
+
+use function PHPStan\Testing\assertSuperType;
+
+$a = 'Alice';
+
+assertSuperType('never', $a);
+assertSuperType('bool', $a);
+assertSuperType('"Bob"', $a);
diff --git a/tests/PHPStan/Testing/data/assert-super-type.php b/tests/PHPStan/Testing/data/assert-super-type.php
new file mode 100644
index 0000000000..1270192d57
--- /dev/null
+++ b/tests/PHPStan/Testing/data/assert-super-type.php
@@ -0,0 +1,8 @@
+<?php
+
+use function PHPStan\Testing\assertSuperType;
+
+$a = 'Alice';
+
+assertSuperType('string', $a);
+assertSuperType('mixed', $a);