Browse Source

fix: Do not shorten FQN for class resolution if imported symbol is not a class (#7705)

Greg Korba 7 months ago
parent
commit
34f53b759c

+ 1 - 1
.github/workflows/ci.yml

@@ -177,7 +177,7 @@ jobs:
         if: env.PHP_MAX == matrix.php-version && matrix.run-migration-rules == 'yes'
         env:
           PHP_CS_FIXER_FUTURE_MODE: 1
-        run: php php-cs-fixer fix --config .php-cs-fixer.php-highest.php -q
+        run: php php-cs-fixer fix --diff --config .php-cs-fixer.php-highest.php
 
       - name: Custom PHPUnit config for code coverage
         if: matrix.collect-code-coverage == 'yes' || matrix.run-mutation-tests

+ 1 - 0
.php-cs-fixer.php-highest.php

@@ -24,6 +24,7 @@ $config = require __DIR__.'/.php-cs-fixer.dist.php';
 $config->setRules(array_merge($config->getRules(), [
     '@PHP83Migration' => true,
     '@PHP80Migration:risky' => true,
+    'fully_qualified_strict_types' => ['import_symbols' => true],
     'php_unit_attributes' => false, // as is not yet supported by PhpCsFixerInternal/configurable_fixer_template
 ]));
 

+ 0 - 12
dev-tools/phpstan/baseline.php

@@ -1219,24 +1219,12 @@ $ignoreErrors[] = [
 	'count' => 2,
 	'path' => __DIR__ . '/../../src/Fixer/Import/FullyQualifiedStrictTypesFixer.php',
 ];
-$ignoreErrors[] = [
-	// identifier: assign.propertyType
-	'message' => '#^Property PhpCsFixer\\\\Fixer\\\\Import\\\\FullyQualifiedStrictTypesFixer\\:\\:\\$discoveredSymbols \\(array\\{const\\?\\: list\\<class\\-string\\>, class\\?\\: list\\<class\\-string\\>, function\\?\\: list\\<class\\-string\\>\\}\\|null\\) does not accept array\\{const\\?\\: list\\<class\\-string\\>, class\\: non\\-empty\\-list\\<string\\>, function\\?\\: list\\<class\\-string\\>\\}\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/Import/FullyQualifiedStrictTypesFixer.php',
-];
 $ignoreErrors[] = [
 	// identifier: assign.propertyType
 	'message' => '#^Property PhpCsFixer\\\\Fixer\\\\Import\\\\FullyQualifiedStrictTypesFixer\\:\\:\\$reservedIdentifiersByLevel \\(array\\<int\\<0, max\\>, array\\<string, true\\>\\>\\) does not accept non\\-empty\\-array\\<int, array\\<string, true\\>\\>\\.$#',
 	'count' => 1,
 	'path' => __DIR__ . '/../../src/Fixer/Import/FullyQualifiedStrictTypesFixer.php',
 ];
-$ignoreErrors[] = [
-	// identifier: assign.propertyType
-	'message' => '#^Property PhpCsFixer\\\\Fixer\\\\Import\\\\FullyQualifiedStrictTypesFixer\\:\\:\\$symbolsForImport \\(array\\{const\\?\\: array\\<string, class\\-string\\>, class\\?\\: array\\<string, class\\-string\\>, function\\?\\: array\\<string, class\\-string\\>\\}\\) does not accept array\\{const\\?\\: array\\<string, string\\>, class\\?\\: array\\<string, string\\>, function\\?\\: array\\<string, string\\>\\}\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/Import/FullyQualifiedStrictTypesFixer.php',
-];
 $ignoreErrors[] = [
 	// identifier: offsetAccess.notFound
 	'message' => '#^Offset 0 might not exist on array\\<list\\<array\\{string, int\\}\\>\\>\\.$#',

+ 3 - 0
phpstan.dist.neon

@@ -49,6 +49,9 @@ parameters:
         # This one is tricky, because adding @param to PHPDoc causes failures on PHP7.4 with lowest deps (phpdoc_to_param_type), using @phpstan-param causes failures on all PHP versions (phpdoc_add_missing_param_annotation)
         - '/^Parameter #1 \$class \(string\) of method PhpCsFixer\\StdinFileInfo::get(File|Path)+Info+\(\) should be contravariant with parameter \$class \(string\|null\) of method SplFileInfo::get(File|Path)+Info\(\)$/'
 
+        # We retrieve these FQNs in various ways, we process them along the way, let's assume it's always class-string
+        - '/^Parameter #2 \$fullName of class PhpCsFixer\\Tokenizer\\Analyzer\\Analysis\\NamespaceUseAnalysis constructor expects class-string, string given\.$/'
+
         # PHPUnit data providers return type were not maintained originally, this exception should shrink over time (eg with help of custom, re-usable type)
         -
             message: '#^Method PhpCsFixer\\Tests\\.+::provide.+Cases\(\) return type has no value type specified in iterable type iterable\.$#'

+ 96 - 56
src/Fixer/Import/FullyQualifiedStrictTypesFixer.php

@@ -54,6 +54,13 @@ use PhpCsFixer\Tokenizer\Tokens;
  *  leading_backslash_in_global_namespace: bool,
  *  phpdoc_tags: list<string>
  * }
+ * @phpstan-type _Uses array{
+ *   constant?: array<class-string, string>,
+ *   class?: array<class-string, string>,
+ *   function?: array<class-string, string>
+ * }
+ *
+ * @phpstan-import-type _ImportType from \PhpCsFixer\Tokenizer\Analyzer\Analysis\NamespaceUseAnalysis
  */
 final class FullyQualifiedStrictTypesFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface
 {
@@ -65,7 +72,7 @@ final class FullyQualifiedStrictTypesFixer extends AbstractFixer implements Conf
 
     /**
      * @var array{
-     *     const?: list<class-string>,
+     *     constant?: list<class-string>,
      *     class?: list<class-string>,
      *     function?: list<class-string>
      * }|null
@@ -74,7 +81,7 @@ final class FullyQualifiedStrictTypesFixer extends AbstractFixer implements Conf
 
     /**
      * @var array{
-     *     const?: array<string, class-string>,
+     *     constant?: array<string, class-string>,
      *     class?: array<string, class-string>,
      *     function?: array<string, class-string>
      * }
@@ -86,13 +93,25 @@ final class FullyQualifiedStrictTypesFixer extends AbstractFixer implements Conf
      */
     private array $reservedIdentifiersByLevel;
 
-    /** @var array<string, string> */
+    /**
+     * @var array{
+     *     constant?: array<string, string>,
+     *     class?: array<string, string>,
+     *     function?: array<string, string>
+     * }
+     */
     private array $cacheUsesLast = [];
 
-    /** @var array<string, string> */
+    /**
+     * @var array{
+     *     constant?: array<string, class-string>,
+     *     class?: array<string, class-string>,
+     *     function?: array<string, class-string>
+     * }
+     */
     private array $cacheUseNameByShortNameLower;
 
-    /** @var array<string, string> */
+    /** @var _Uses */
     private array $cacheUseShortNameByNameLower;
 
     public function getDefinition(): FixerDefinitionInterface
@@ -281,6 +300,8 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
             $namespace = $tokens->getNamespaceDeclarations()[$namespaceIndex];
 
             $namespaceName = $namespace->getFullName();
+
+            /** @var _Uses $uses */
             $uses = [];
             $lastUse = null;
 
@@ -288,7 +309,8 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
                 if (!$use->isClass()) {
                     continue;
                 }
-                $uses[ltrim($use->getFullName(), '\\')] = $use->getShortName();
+
+                $uses[$use->getHumanFriendlyType()][ltrim($use->getFullName(), '\\')] = $use->getShortName();
                 $lastUse = $use;
             }
 
@@ -332,7 +354,9 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
                     } elseif (\defined('T_ATTRIBUTE') && $tokens[$index]->isGivenKind(T_ATTRIBUTE)) { // @TODO: drop const check when PHP 8.0+ is required
                         $this->fixAttribute($tokens, $index, $uses, $namespaceName);
                     } elseif ($discoverSymbolsPhase && !\defined('T_ATTRIBUTE') && $tokens[$index]->isComment() && Preg::match('/#\[\s*('.self::REGEX_CLASS.')/', $tokens[$index]->getContent(), $matches)) { // @TODO: drop when PHP 8.0+ is required
-                        $this->determineShortType($matches[1], $uses, $namespaceName);
+                        /** @var class-string $attributeClass */
+                        $attributeClass = $matches[1];
+                        $this->determineShortType($attributeClass, 'class', $uses, $namespaceName);
                     } elseif ($tokens[$index]->isGivenKind(T_DOC_COMMENT)) {
                         Preg::matchAll('/\*\h*@(?:psalm-|phpstan-)?(?:template(?:-covariant|-contravariant)?|(?:import-)?type)\h+('.TypeExpression::REGEX_IDENTIFIER.')(?!\S)/i', $tokens[$index]->getContent(), $matches);
                         foreach ($matches[1] as $reservedIdentifier) {
@@ -375,7 +399,7 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     }
 
     /**
-     * @param array<string, string> $uses
+     * @param _Uses $uses
      */
     private function refreshUsesCache(array $uses): void
     {
@@ -384,11 +408,17 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
         }
 
         $this->cacheUsesLast = $uses;
-        $this->cacheUseNameByShortNameLower = [];
-        $this->cacheUseShortNameByNameLower = [];
-        foreach ($uses as $useLongName => $useShortName) {
-            $this->cacheUseNameByShortNameLower[strtolower($useShortName)] = $useLongName;
-            $this->cacheUseShortNameByNameLower[strtolower($useLongName)] = $useShortName;
+
+        foreach ($uses as $kind => $kindUses) {
+            $this->cacheUseNameByShortNameLower = [];
+            $this->cacheUseShortNameByNameLower = [];
+            foreach ($kindUses as $useLongName => $useShortName) {
+                $this->cacheUseNameByShortNameLower[$kind][strtolower($useShortName)] = $useLongName;
+
+                /** @var class-string $normalisedUseLongName */
+                $normalisedUseLongName = strtolower($useLongName);
+                $this->cacheUseShortNameByNameLower[$kind][$normalisedUseLongName] = $useShortName;
+            }
         }
     }
 
@@ -414,35 +444,40 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     /**
      * Resolve absolute or relative symbol to normalized FQCN.
      *
-     * @param array<string, string> $uses
+     * @param _ImportType $importKind
+     * @param _Uses       $uses
+     *
+     * @return class-string
      */
-    private function resolveSymbol(string $symbol, array $uses, string $namespaceName): string
+    private function resolveSymbol(string $symbol, string $importKind, array $uses, string $namespaceName): string
     {
         if (str_starts_with($symbol, '\\')) {
-            return substr($symbol, 1);
+            return substr($symbol, 1); // @phpstan-ignore return.type
         }
 
         if ($this->isReservedIdentifier($symbol)) {
-            return $symbol;
+            return $symbol; // @phpstan-ignore return.type
         }
 
         $this->refreshUsesCache($uses);
 
         $symbolArr = explode('\\', $symbol, 2);
         $shortStartNameLower = strtolower($symbolArr[0]);
-        if (isset($this->cacheUseNameByShortNameLower[$shortStartNameLower])) {
-            return $this->cacheUseNameByShortNameLower[$shortStartNameLower].(isset($symbolArr[1]) ? '\\'.$symbolArr[1] : '');
+        if (isset($this->cacheUseNameByShortNameLower[$importKind][$shortStartNameLower])) {
+            // @phpstan-ignore return.type
+            return $this->cacheUseNameByShortNameLower[$importKind][$shortStartNameLower].(isset($symbolArr[1]) ? '\\'.$symbolArr[1] : '');
         }
 
-        return ('' !== $namespaceName ? $namespaceName.'\\' : '').$symbol;
+        return ('' !== $namespaceName ? $namespaceName.'\\' : '').$symbol; // @phpstan-ignore return.type
     }
 
     /**
      * Shorten normalized FQCN as much as possible.
      *
-     * @param array<string, string> $uses
+     * @param _ImportType $importKind
+     * @param _Uses       $uses
      */
-    private function shortenSymbol(string $fqcn, array $uses, string $namespaceName): string
+    private function shortenSymbol(string $fqcn, string $importKind, array $uses, string $namespaceName): string
     {
         if ($this->isReservedIdentifier($fqcn)) {
             return $fqcn;
@@ -456,7 +491,7 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
         $iMin = 0;
         if (str_starts_with($fqcn, $namespaceName.'\\')) {
             $tmpRes = substr($fqcn, \strlen($namespaceName) + 1);
-            if (!isset($this->cacheUseNameByShortNameLower[strtolower(explode('\\', $tmpRes, 2)[0])]) && !$this->isReservedIdentifier($tmpRes)) {
+            if (!isset($this->cacheUseNameByShortNameLower[$importKind][strtolower(explode('\\', $tmpRes, 2)[0])]) && !$this->isReservedIdentifier($tmpRes)) {
                 $res = $tmpRes;
                 $iMin = substr_count($namespaceName, '\\') + 1;
             }
@@ -465,8 +500,8 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
         // try to shorten the name using uses
         $tmp = $fqcn;
         for ($i = substr_count($fqcn, '\\'); $i >= $iMin; --$i) {
-            if (isset($this->cacheUseShortNameByNameLower[strtolower($tmp)])) {
-                $tmpRes = $this->cacheUseShortNameByNameLower[strtolower($tmp)].substr($fqcn, \strlen($tmp));
+            if (isset($this->cacheUseShortNameByNameLower[$importKind][strtolower($tmp)])) {
+                $tmpRes = $this->cacheUseShortNameByNameLower[$importKind][strtolower($tmp)].substr($fqcn, \strlen($tmp));
                 if (!$this->isReservedIdentifier($tmpRes)) {
                     $res = $tmpRes;
 
@@ -484,7 +519,7 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
             $res = $fqcn;
             if ('' !== $namespaceName
                 || true === $this->configuration['leading_backslash_in_global_namespace']
-                || isset($this->cacheUseNameByShortNameLower[strtolower(explode('\\', $res, 2)[0])])
+                || isset($this->cacheUseNameByShortNameLower[$importKind][strtolower(explode('\\', $res, 2)[0])])
             ) {
                 $res = '\\'.$res;
             }
@@ -494,7 +529,7 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     }
 
     /**
-     * @param array<string, string> $uses
+     * @param _Uses $uses
      */
     private function setupUsesFromDiscoveredSymbols(array &$uses, string $namespaceName): void
     {
@@ -506,17 +541,17 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
                     if (!str_starts_with($symbol, '\\')) {
                         $shortStartName = explode('\\', ltrim($symbol, '\\'), 2)[0];
                         $shortStartNameLower = strtolower($shortStartName);
-                        $discoveredFqcnByShortNameLower[$shortStartNameLower] = $this->resolveSymbol($shortStartName, $uses, $namespaceName);
+                        $discoveredFqcnByShortNameLower[$kind][$shortStartNameLower] = $this->resolveSymbol($shortStartName, $kind, $uses, $namespaceName);
                     }
                 }
             }
 
-            foreach ($uses as $useLongName => $useShortName) {
-                $discoveredFqcnByShortNameLower[strtolower($useShortName)] = $useLongName;
+            foreach ($uses[$kind] ?? [] as $useLongName => $useShortName) {
+                $discoveredFqcnByShortNameLower[$kind][strtolower($useShortName)] = $useLongName;
             }
 
             $useByShortNameLower = [];
-            foreach ($uses as $useShortName) {
+            foreach ($uses[$kind] ?? [] as $useShortName) {
                 $useByShortNameLower[strtolower($useShortName)] = true;
             }
 
@@ -531,12 +566,12 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
             foreach ($discoveredSymbols as $symbol) {
                 while (true) {
                     $shortEndNameLower = strtolower(str_contains($symbol, '\\') ? substr($symbol, strrpos($symbol, '\\') + 1) : $symbol);
-                    if (!isset($discoveredFqcnByShortNameLower[$shortEndNameLower])) {
+                    if (!isset($discoveredFqcnByShortNameLower[$kind][$shortEndNameLower])) {
                         $shortStartNameLower = strtolower(explode('\\', ltrim($symbol, '\\'), 2)[0]);
                         if (str_starts_with($symbol, '\\') || ('' === $namespaceName && !isset($useByShortNameLower[$shortStartNameLower]))
                             || !str_contains($symbol, '\\')
                         ) {
-                            $discoveredFqcnByShortNameLower[$shortEndNameLower] = $this->resolveSymbol($symbol, $uses, $namespaceName);
+                            $discoveredFqcnByShortNameLower[$kind][$shortEndNameLower] = $this->resolveSymbol($symbol, $kind, $uses, $namespaceName);
 
                             break;
                         }
@@ -551,18 +586,18 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
                 }
             }
 
-            foreach ($uses as $useLongName => $useShortName) {
-                $discoveredLongName = $discoveredFqcnByShortNameLower[strtolower($useShortName)] ?? null;
+            foreach ($uses[$kind] ?? [] as $useLongName => $useShortName) {
+                $discoveredLongName = $discoveredFqcnByShortNameLower[$kind][strtolower($useShortName)] ?? null;
                 if (strtolower($discoveredLongName) === strtolower($useLongName)) {
-                    unset($discoveredFqcnByShortNameLower[strtolower($useShortName)]);
+                    unset($discoveredFqcnByShortNameLower[$kind][strtolower($useShortName)]);
                 }
             }
 
-            foreach ($discoveredFqcnByShortNameLower as $fqcn) {
-                $shortenedName = ltrim($this->shortenSymbol($fqcn, [], $namespaceName), '\\');
+            foreach ($discoveredFqcnByShortNameLower[$kind] ?? [] as $fqcn) {
+                $shortenedName = ltrim($this->shortenSymbol($fqcn, $kind, [], $namespaceName), '\\');
                 if (str_contains($shortenedName, '\\')) { // prevent importing non-namespaced names in global namespace
                     $shortEndName = str_contains($fqcn, '\\') ? substr($fqcn, strrpos($fqcn, '\\') + 1) : $fqcn;
-                    $uses[$fqcn] = $shortEndName;
+                    $uses[$kind][$fqcn] = $shortEndName;
                     $this->symbolsForImport[$kind][$shortEndName] = $fqcn;
                 }
             }
@@ -574,7 +609,7 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     }
 
     /**
-     * @param array<string, string> $uses
+     * @param _Uses $uses
      */
     private function fixFunction(FunctionsAnalyzer $functionsAnalyzer, Tokens $tokens, int $index, array $uses, string $namespaceName): void
     {
@@ -596,7 +631,7 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     }
 
     /**
-     * @param array<string, string> $uses
+     * @param _Uses $uses
      */
     private function fixPhpDoc(Tokens $tokens, int $index, array $uses, string $namespaceName): void
     {
@@ -617,13 +652,14 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
             $unsupported = false;
 
             return $matches[1].$matches[2].$matches[3].implode('|', array_map(function ($v) use ($uses, $namespaceName, &$unsupported) {
+                /** @var class-string $v */
                 if ($unsupported || !Preg::match('/^'.self::REGEX_CLASS.'$/', $v)) {
                     $unsupported = true;
 
                     return $v;
                 }
 
-                $shortTokens = $this->determineShortType($v, $uses, $namespaceName);
+                $shortTokens = $this->determineShortType($v, 'class', $uses, $namespaceName);
                 if (null === $shortTokens) {
                     return $v;
                 }
@@ -641,7 +677,7 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     }
 
     /**
-     * @param array<string, string> $uses
+     * @param _Uses $uses
      */
     private function fixExtendsImplements(Tokens $tokens, int $index, array $uses, string $namespaceName): void
     {
@@ -674,7 +710,7 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     }
 
     /**
-     * @param array<string, string> $uses
+     * @param _Uses $uses
      */
     private function fixCatch(Tokens $tokens, int $index, array $uses, string $namespaceName): void
     {
@@ -708,7 +744,7 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     }
 
     /**
-     * @param array<string, string> $uses
+     * @param _Uses $uses
      */
     private function fixAttribute(Tokens $tokens, int $index, array $uses, string $namespaceName): void
     {
@@ -724,7 +760,7 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     }
 
     /**
-     * @param array<string, string> $uses
+     * @param _Uses $uses
      */
     private function fixPrevName(Tokens $tokens, int $index, array $uses, string $namespaceName): void
     {
@@ -753,7 +789,7 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     }
 
     /**
-     * @param array<string, string> $uses
+     * @param _Uses $uses
      */
     private function fixNextName(Tokens $tokens, int $index, array $uses, string $namespaceName): void
     {
@@ -779,12 +815,13 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     }
 
     /**
-     * @param array<string, string> $uses
+     * @param _Uses $uses
      */
     private function shortenClassIfPossible(Tokens $tokens, int $typeStartIndex, int $typeEndIndex, array $uses, string $namespaceName): int
     {
+        /** @var class-string $content */
         $content = $tokens->generatePartialCode($typeStartIndex, $typeEndIndex);
-        $newTokens = $this->determineShortType($content, $uses, $namespaceName);
+        $newTokens = $this->determineShortType($content, 'class', $uses, $namespaceName);
         if (null === $newTokens) {
             return 0;
         }
@@ -795,7 +832,7 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     }
 
     /**
-     * @param array<string, string> $uses
+     * @param _Uses $uses
      */
     private function replaceByShortType(Tokens $tokens, TypeAnalysis $type, array $uses, string $namespaceName): void
     {
@@ -808,8 +845,9 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
         $types = $this->getTypes($tokens, $typeStartIndex, $type->getEndIndex());
 
         foreach ($types as [$startIndex, $endIndex]) {
+            /** @var class-string $content */
             $content = $tokens->generatePartialCode($startIndex, $endIndex);
-            $newTokens = $this->determineShortType($content, $uses, $namespaceName);
+            $newTokens = $this->determineShortType($content, 'class', $uses, $namespaceName);
             if (null !== $newTokens) {
                 $tokens->overrideRange($startIndex, $endIndex, $newTokens);
             }
@@ -819,22 +857,24 @@ class Foo extends \Other\BaseClass implements \Other\Interface1, \Other\Interfac
     /**
      * Determines short type based on FQCN, current namespace and imports (`use` declarations).
      *
-     * @param array<string, string> $uses
+     * @param class-string $typeName
+     * @param _ImportType  $importKind
+     * @param _Uses        $uses
      *
      * @return null|list<Token>
      */
-    private function determineShortType(string $typeName, array $uses, string $namespaceName): ?array
+    private function determineShortType(string $typeName, string $importKind, array $uses, string $namespaceName): ?array
     {
         if (null !== $this->discoveredSymbols) {
             if (!$this->isReservedIdentifier($typeName)) {
-                $this->discoveredSymbols['class'][] = $typeName;
+                $this->discoveredSymbols[$importKind][] = $typeName;
             }
 
             return null;
         }
 
-        $fqcn = $this->resolveSymbol($typeName, $uses, $namespaceName);
-        $shortenedType = $this->shortenSymbol($fqcn, $uses, $namespaceName);
+        $fqcn = $this->resolveSymbol($typeName, $importKind, $uses, $namespaceName);
+        $shortenedType = $this->shortenSymbol($fqcn, $importKind, $uses, $namespaceName);
         if ($shortenedType === $typeName) {
             return null;
         }

+ 20 - 0
src/Tokenizer/Analyzer/Analysis/NamespaceUseAnalysis.php

@@ -19,6 +19,8 @@ namespace PhpCsFixer\Tokenizer\Analyzer\Analysis;
  * @author Greg Korba <greg@codito.dev>
  *
  * @internal
+ *
+ * @phpstan-type _ImportType 'class'|'constant'|'function'
  */
 final class NamespaceUseAnalysis implements StartEndTokenAwareAnalysis
 {
@@ -28,6 +30,8 @@ final class NamespaceUseAnalysis implements StartEndTokenAwareAnalysis
 
     /**
      * The fully qualified use namespace.
+     *
+     * @var class-string
      */
     private string $fullName;
 
@@ -75,6 +79,7 @@ final class NamespaceUseAnalysis implements StartEndTokenAwareAnalysis
 
     /**
      * @param self::TYPE_* $type
+     * @param class-string $fullName
      */
     public function __construct(
         int $type,
@@ -102,6 +107,9 @@ final class NamespaceUseAnalysis implements StartEndTokenAwareAnalysis
         $this->chunkEndIndex = $chunkEndIndex;
     }
 
+    /**
+     * @return class-string
+     */
     public function getFullName(): string
     {
         return $this->fullName;
@@ -150,6 +158,18 @@ final class NamespaceUseAnalysis implements StartEndTokenAwareAnalysis
         return $this->type;
     }
 
+    /**
+     * @return _ImportType
+     */
+    public function getHumanFriendlyType(): string
+    {
+        return [
+            self::TYPE_CLASS => 'class',
+            self::TYPE_FUNCTION => 'function',
+            self::TYPE_CONSTANT => 'constant',
+        ][$this->type];
+    }
+
     public function isClass(): bool
     {
         return self::TYPE_CLASS === $this->type;

+ 6 - 3
src/Tokenizer/Analyzer/NamespaceUsesAnalyzer.php

@@ -112,7 +112,7 @@ final class NamespaceUsesAnalyzer
                     $groupQualifiedName = $this->getNearestQualifiedName($tokens, $chunkStart);
                     $imports[] = new NamespaceUseAnalysis(
                         $type,
-                        $qualifiedName['fullName'].$groupQualifiedName['fullName'],
+                        $qualifiedName['fullName'].$groupQualifiedName['fullName'], // @phpstan-ignore argument.type
                         $groupQualifiedName['shortName'],
                         $groupQualifiedName['aliased'],
                         true,
@@ -171,7 +171,7 @@ final class NamespaceUsesAnalyzer
     }
 
     /**
-     * @return array{fullName: string, shortName: string, aliased: bool, afterIndex: int}
+     * @return array{fullName: class-string, shortName: string, aliased: bool, afterIndex: int}
      */
     private function getNearestQualifiedName(Tokens $tokens, int $index): array
     {
@@ -203,8 +203,11 @@ final class NamespaceUsesAnalyzer
             $index = $tokens->getNextMeaningfulToken($index);
         }
 
+        /** @var class-string $fqn */
+        $fqn = $fullName;
+
         return [
-            'fullName' => $fullName,
+            'fullName' => $fqn,
             'shortName' => $shortName,
             'aliased' => $aliased,
             'afterIndex' => $index,

+ 8 - 0
tests/Fixer/Import/FullyQualifiedStrictTypesFixerTest.php

@@ -71,6 +71,14 @@ function test(\Foo\Bar $x) {}',
 function test(int $x): void {}',
         ];
 
+        yield 'ignore class resolution shortening when imported symbol is a function' => [
+            '<?php
+
+use function Symfony\Component\String\u;
+
+echo Symfony\Component\String\u::class;',
+        ];
+
         yield 'namespace cases' => [
             '<?php