Repository: PHP-CS-Fixer/PHP-CS-Fixer (PHP)

Issue:
fully_qualified_strict_types should be considered risky or updated
## Bug report
fully_qualified_strict_types rule replaces namespace even if the resulting class name is overriden by the `use` command. Result could be a PHP error.

### Code snippet that reproduces the problem
```
namespace A\B;

require "ABClazz.php";
require "AClazz.php";

use A\Clazz;

class Service
{
    public function __construct(
        private \A\B\Clazz $clz
    ) {
    }
}

new Service(new \A\B\Clazz());
```
ABClazz.php:
```
namespace A\B;
class Clazz {}
```

AClazz.php:
```
namespace A;
class Clazz {}
```

Result after php-cs-fixer:
```diff
    public function __construct(
-        private \A\B\Clazz $clz
+        private Clazz $clz
    ) {
```
Namespace in Service constructor is removed so Clazz from A\Clazz is used instead of A\B\Clazz. It leads to PHP error when making new instance of Service. I guess it should be considered there is a namespace conflict and replacement should be avoided. Or this directive should be considered risky if it can lead to error.


Discussion:
Of course, this is a bug, marking fixer risky because it breaks code is nonsense.



Patch:
diff --git a/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php b/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php
index bd8c91d7aec..6c52a9c1877 100644
--- a/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php
+++ b/src/Fixer/Import/FullyQualifiedStrictTypesFixer.php
@@ -177,6 +177,17 @@ private function replaceByShortType(Tokens $tokens, TypeAnalysis $type, array $u
             } elseif ($typeNameLower !== $namespaceName && str_starts_with($typeNameLower, $namespaceName)) {
                 // if the type starts with namespace and the type is not the same as the namespace it can be shortened
                 $typeNameShort = substr($typeName, $namespaceNameLength + 1);
+
+                // if short names are the same, but long one are different then it cannot be shortened
+                foreach ($uses as $useLongName => $useShortName) {
+                    if (
+                        strtolower($typeNameShort) === strtolower($useShortName)
+                        && strtolower($typeName) !== strtolower($useLongName)
+                    ) {
+                        continue 2;
+                    }
+                }
+
                 $tokens->overrideRange($startIndex, $endIndex, $this->namespacedStringToTokens($typeNameShort));
             }
         }


Test patch:
diff --git a/tests/Fixer/Import/FullyQualifiedStrictTypesFixerTest.php b/tests/Fixer/Import/FullyQualifiedStrictTypesFixerTest.php
index cdcd06f38ec..16a4db326f6 100644
--- a/tests/Fixer/Import/FullyQualifiedStrictTypesFixerTest.php
+++ b/tests/Fixer/Import/FullyQualifiedStrictTypesFixerTest.php
@@ -527,7 +527,7 @@ class SomeClass
     public function doSomething(
         \Ping\Something $something,
         Pung\Pang $other,
-        Pung $other1,
+        \Ping\Pong\Pung $other1,
         Pang\Pung $other2,
         Pyng\Pung\Pong $other3,
         \Foo\Bar\Baz\Buz $other4
@@ -576,6 +576,18 @@ function withReference(\Exception &$e) {}
     }
 ',
         ];
+
+        yield 'Test FQCN is not removed when class with the same name, but different namespace, is imported' => [
+            '<?php namespace Foo;
+                use Bar\TheClass;
+                class Test
+                {
+                    public function __construct(
+                        \Foo\TheClass $x
+                    ) {}
+                }
+            ',
+        ];
     }
 
     public static function provideCodeWithReturnTypesCasesWithNullableCases(): array