Repository: phpactor/phpactor (php)

Issue:
False positive for missing method param
```
     * @param array<'GET'|'POST'|'PUT'|'DELETE'|'PATCH'> $methods
     * @param array<string,string> $requirements
     * @param array<int,AbstractAnnotation> $openApiAttributes
     * @param array<string,mixed> $defaults
     */
    public function __construct(
        public string $name,
        public string $path,
        public string $controller,
        public array $methods,
        public array $requirements,
        public array $defaults,
        public array $openApiAttributes,
    )
    {
    }

```

![image](https://github.com/phpactor/phpactor/assets/530801/0516be7f-6c40-4236-9fc4-39c4986f0981)


Pull request description:
Gh 2264
Fix #2264 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ecdbbe14..731a4557 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ Features:
 Bug fixes:
 
   - Fix behavior when user cancels type selection on goto type #2270
+  - Fix docblock parsing of `array<'quoted'|'strings'>` #2264
   - Fix constant declaration indexing with `define` #2249 @mamazu
   - Fix use of class-string<Foo> variable as static scope resolution qualifier #2238
   - URL decode root URI - fixes issues with special chars in path #2228
diff --git a/lib/DocblockParser/Parser.php b/lib/DocblockParser/Parser.php
index f8995524..f0802197 100644
--- a/lib/DocblockParser/Parser.php
+++ b/lib/DocblockParser/Parser.php
@@ -309,6 +309,9 @@ final class Parser
             if ($this->tokens->if(Token::T_VARIABLE)) {
                 $typeList = $this->parseTypeList();
             }
+            if ($this->tokens->if(Token::T_QUOTED_STRING)) {
+                $typeList = $this->parseTypeList();
+            }
             if ($this->tokens->if(Token::T_LABEL)) {
                 $typeList = $this->parseTypeList();
             }
@@ -425,6 +428,8 @@ final class Parser
         while (true) {
             if ($this->tokens->if(Token::T_LABEL)) {
                 $types[] = $this->parseTypes();
+            } elseif ($this->tokens->if(Token::T_QUOTED_STRING)) {
+                $types[] = $this->parseTypes();
             } elseif ($this->tokens->if(Token::T_INTEGER)) {
                 $types[] = $this->parseTypes();
             } elseif ($this->tokens->if(Token::T_VARIABLE)) {
diff --git a/lib/WorseReflection/Bridge/TolerantParser/Diagnostics/DocblockMissingParamProvider.php b/lib/WorseReflection/Bridge/TolerantParser/Diagnostics/DocblockMissingParamProvider.php
index fca0ec9f..c6faa183 100644
--- a/lib/WorseReflection/Bridge/TolerantParser/Diagnostics/DocblockMissingParamProvider.php
+++ b/lib/WorseReflection/Bridge/TolerantParser/Diagnostics/DocblockMissingParamProvider.php
@@ -180,6 +180,25 @@ class DocblockMissingParamProvider implements DiagnosticProvider
                 Assert::assertEquals('Method "foo" is missing @param $foobar', $diagnostics->at(0)->message());
             }
         );
+        yield new DiagnosticExample(
+            title: 'no false positive for union of scalars',
+            source: <<<'PHP'
+                <?php
+
+                class Foobar
+                {
+                    /**
+                     * @param array<'GET'|'POST'> $foobar
+                     */
+                    public function foo(array $foobar) {
+                    }
+                }
+                PHP,
+            valid: true,
+            assertion: function (Diagnostics $diagnostics): void {
+                Assert::assertCount(0, $diagnostics);
+            }
+        );
         yield new DiagnosticExample(
             title: 'does not report diagnostic on method with @param',
             source: <<<'PHP'


Test patch:
diff --git a/lib/DocblockParser/Tests/Unit/Printer/examples/array_union_ints.test b/lib/DocblockParser/Tests/Unit/Printer/examples/array_union_ints.test
new file mode 100644
index 00000000..26706f44
--- /dev/null
+++ b/lib/DocblockParser/Tests/Unit/Printer/examples/array_union_ints.test
@@ -0,0 +1,15 @@
+/** @param array<12|13|14> $foobar */
+---
+Docblock: = 
+ ElementList: = /** 
+  ParamTag: = @param
+   GenericNode: = 
+    ArrayNode: = array<
+    TypeList: = 
+     UnionNode: = 
+      TypeList: = 
+       LiteralIntegerNode: = 12|
+       LiteralIntegerNode: = 13|
+       LiteralIntegerNode: = 14>
+   VariableNode: = $foobar
+   TextNode: =  */
diff --git a/lib/DocblockParser/Tests/Unit/Printer/examples/array_union_strings.test b/lib/DocblockParser/Tests/Unit/Printer/examples/array_union_strings.test
new file mode 100644
index 00000000..c261a216
--- /dev/null
+++ b/lib/DocblockParser/Tests/Unit/Printer/examples/array_union_strings.test
@@ -0,0 +1,15 @@
+/** @param array<'GET'|'POST'|'DELETE'> $foobar */
+---
+Docblock: = 
+ ElementList: = /** 
+  ParamTag: = @param
+   GenericNode: = 
+    ArrayNode: = array<
+    TypeList: = 
+     UnionNode: = 
+      TypeList: = 
+       LiteralStringNode: = 'GET'|
+       LiteralStringNode: = 'POST'|
+       LiteralStringNode: = 'DELETE'>
+   VariableNode: = $foobar
+   TextNode: =  */