Repository: phpactor/phpactor (php)

Issue:
Complete constructor params inside attribute usage
We have such completion available when calling the constructor directly
![image](https://github.com/phpactor/phpactor/assets/11404453/6b82797c-ab96-4659-9aa9-7c5a514df64c)
but not if we call it from an attribute node. 
![image](https://github.com/phpactor/phpactor/assets/11404453/fba675b1-4536-4aa3-b6b1-25cb40db1dbb)


Pull request description:
Suggesting named constructor params on attributes
Fixes #2495 

Patch:
diff --git a/lib/Completion/Bridge/TolerantParser/WorseReflection/WorseNamedParameterCompletor.php b/lib/Completion/Bridge/TolerantParser/WorseReflection/WorseNamedParameterCompletor.php
index 6b0b4c28..c0987611 100644
--- a/lib/Completion/Bridge/TolerantParser/WorseReflection/WorseNamedParameterCompletor.php
+++ b/lib/Completion/Bridge/TolerantParser/WorseReflection/WorseNamedParameterCompletor.php
@@ -11,6 +11,7 @@ use Microsoft\PhpParser\Node\Expression\MemberAccessExpression;
 use Microsoft\PhpParser\Node\Expression\ObjectCreationExpression;
 use Microsoft\PhpParser\Node\Expression\ScopedPropertyAccessExpression;
 use Microsoft\PhpParser\Node\Expression\Variable;
+use Microsoft\PhpParser\Node\Attribute;
 use Microsoft\PhpParser\Node\QualifiedName;
 use Microsoft\PhpParser\Node\StringLiteral;
 use Phpactor\Completion\Bridge\TolerantParser\TolerantCompletor;
@@ -41,6 +42,7 @@ class WorseNamedParameterCompletor implements TolerantCompletor
             [
                 MemberAccessExpression::class,
                 ObjectCreationExpression::class,
+                Attribute::class,
                 CallExpression::class,
             ],
             [
@@ -61,6 +63,10 @@ class WorseNamedParameterCompletor implements TolerantCompletor
             return yield from $this->fromObjectCreation($subject);
         }
 
+        if ($subject instanceof Attribute) {
+            return yield from $this->fromAttribute($subject);
+        }
+
         if ($subject instanceof CallExpression) {
             return yield from $this->fromCallExpression($subject);
         }
@@ -68,6 +74,9 @@ class WorseNamedParameterCompletor implements TolerantCompletor
         return true;
     }
 
+    /**
+     * @return Generator<Suggestion>
+     */
     private function fromObjectCreation(ObjectCreationExpression $creation): Generator
     {
         $type = $creation->classTypeDesignator;
@@ -87,6 +96,32 @@ class WorseNamedParameterCompletor implements TolerantCompletor
         return true;
     }
 
+    /**
+     * @return Generator<Suggestion>
+     */
+    private function fromAttribute(Attribute $attribute): Generator
+    {
+        /** @var QualifiedName|null $type */
+        $type = $attribute->name;
+
+        if (!$type instanceof QualifiedName) {
+            return true;
+        }
+
+        try {
+            $class = $this->reflector->reflectClass((string)$type->getResolvedName());
+        } catch (NotFound) {
+            return true;
+        }
+
+        yield from $this->fromMethod($class, '__construct');
+
+        return true;
+    }
+
+    /**
+     * @return Generator<Suggestion>
+     */
     private function fromMethod(ReflectionClassLike $class, string $method): Generator
     {
         if (!$class->methods()->has($method)) {
@@ -105,6 +140,9 @@ class WorseNamedParameterCompletor implements TolerantCompletor
         }
     }
 
+    /**
+     * @return Generator<Suggestion>
+     */
     private function fromFunction(ReflectionFunction $function): Generator
     {
         foreach ($function->parameters() as $parameter) {
@@ -119,6 +157,9 @@ class WorseNamedParameterCompletor implements TolerantCompletor
         }
     }
 
+    /**
+     * @return Generator<Suggestion>
+     */
     private function fromCallExpression(CallExpression $creation): Generator
     {
         /** @var Node */
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index e0ca5408..6ff9700e 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -760,11 +760,6 @@ parameters:
 			count: 1
 			path: lib/Completion/Bridge/TolerantParser/WorseReflection/WorseFunctionCompletor.php
 
-		-
-			message: "#^Generator expects value type Phpactor\\\\Completion\\\\Core\\\\Suggestion, mixed given\\.$#"
-			count: 2
-			path: lib/Completion/Bridge/TolerantParser/WorseReflection/WorseNamedParameterCompletor.php
-
 		-
 			message: "#^Method Phpactor\\\\Completion\\\\Core\\\\ChainSignatureHelper\\:\\:__construct\\(\\) has parameter \\$helpers with no value type specified in iterable type array\\.$#"
 			count: 1
@@ -4580,11 +4575,6 @@ parameters:
 			count: 1
 			path: lib/Extension/PhpCodeSniffer/Model/PhpCodeSnifferProcess.php
 
-		-
-			message: "#^Parameter \\#3 \\$env of class Phpactor\\\\Extension\\\\PhpCodeSniffer\\\\Model\\\\PhpCodeSnifferProcess constructor expects array\\<string, string\\>, mixed given\\.$#"
-			count: 1
-			path: lib/Extension/PhpCodeSniffer/PhpCodeSnifferExtension.php
-
 		-
 			message: "#^Parameter \\#3 \\$workspace of class Phpactor\\\\Extension\\\\PhpCodeSniffer\\\\LspCommand\\\\FormatCommand constructor expects Phpactor\\\\LanguageServer\\\\Core\\\\Workspace\\\\Workspace, mixed given\\.$#"
 			count: 1
@@ -6355,11 +6345,6 @@ parameters:
 			count: 1
 			path: lib/WorseReflection/Core/Inference/PropertyAssignments.php
 
-		-
-			message: "#^Parameter \\#2 \\$node of method Phpactor\\\\WorseReflection\\\\Core\\\\Inference\\\\NodeContextResolver\\:\\:resolveNode\\(\\) expects Microsoft\\\\PhpParser\\\\Node\\|Microsoft\\\\PhpParser\\\\Token, Microsoft\\\\PhpParser\\\\Node\\\\Expression\\|null given\\.$#"
-			count: 1
-			path: lib/WorseReflection/Core/Inference/Resolver/ArgumentExpressionResolver.php
-
 		-
 			message: "#^Cannot access property \\$elementKey on mixed\\.$#"
 			count: 2


Test patch:
diff --git a/lib/Completion/Tests/Integration/Bridge/TolerantParser/WorseReflection/WorseNamedParameterCompletorTest.php b/lib/Completion/Tests/Integration/Bridge/TolerantParser/WorseReflection/WorseNamedParameterCompletorTest.php
index 8c70df83..f6a147f6 100644
--- a/lib/Completion/Tests/Integration/Bridge/TolerantParser/WorseReflection/WorseNamedParameterCompletorTest.php
+++ b/lib/Completion/Tests/Integration/Bridge/TolerantParser/WorseReflection/WorseNamedParameterCompletorTest.php
@@ -109,6 +109,26 @@ class WorseNamedParameterCompletorTest extends TolerantCompletorTestCase
                 ]
             ]
         ];
+
+        yield 'Attributes' => [
+            <<<PHP
+                <?php
+                #[Attribute]
+                class SomeAttribute {
+                    public function __construct(private string \$param) {}
+                }
+
+                #[SomeAttribute(<>)]
+                class Foo {}
+                PHP,
+            [
+                [
+                    'type' => Suggestion::TYPE_FIELD,
+                    'name' => 'param: ',
+                    'short_description' => 'string $param',
+                ]
+            ]
+        ];
     }
 
     /**