Repository: phpactor/phpactor (php)

Issue:
Exception when selected commented out code
When selecting commented out code an error is produced. In my case the code was:
```php
// if (count($values) == 0) {
```
The exception is:
```
  Message: Exception [AssertionError] assert($startNode instanceof Node) at /root/.shared_extensions/phpactor2/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseExtractMethod.php#102
  Code: -32603 
#0 /root/.shared_extensions/phpactor2/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseExtractMethod.php(102): assert(false, 'assert($startNo...')
#1 /root/.shared_extensions/phpactor2/lib/Extension/LanguageServerCodeTransform/CodeAction/ExtractMethodProvider.php(43): Phpactor\CodeTransform\Adapter\WorseReflection\Refactor\WorseExtractMethod->canExtractMethod(Object(Phpactor\CodeTransform\Domain\SourceCode), 9380, 9409)
#2 /root/.shared_extensions/phpactor2/vendor/amphp/amp/lib/functions.php(90): Phpactor\Extension\LanguageServerCodeTransform\CodeAction\ExtractMethodProvider->Phpactor\Extension\LanguageServerCodeTransform\CodeAction\{closure}()
#3 /root/.shared_extensions/phpactor2/lib/Extension/LanguageServerCodeTransform/CodeAction/ExtractMethodProvider.php(64): Amp\call(Object(Closure))
#4 /root/.shared_extensions/phpactor2/vendor/phpactor/language-server/lib/Core/CodeAction/AggregateCodeActionProvider.php(32): Phpactor\Extension\LanguageServerCodeTransform\CodeAction\ExtractMethodProvider->provideActionsFor(Object(Phpactor\LanguageServerProtocol\TextDocumentItem), Object(Phpactor\LanguageServerProtocol\Range), Object(Amp\CancellationToken@anonymous))
#5 [internal function]: Phpactor\LanguageServer\Core\CodeAction\AggregateCodeActionProvider->Phpactor\LanguageServer\Core\CodeAction\{closure}()
#6 /root/.shared_extensions/phpactor2/vendor/amphp/amp/lib/Coroutine.php(118): Generator->send(NULL)
#7 /root/.shared_extensions/phpactor2/vendor/amphp/amp/lib/Internal/Placeholder.php(149): Amp\Coroutine->Amp\{closure}(NULL, NULL)
#8 /root/.shared_extensions/phpactor2/vendor/amphp/amp/lib/Delayed.php(26): Amp\Delayed->resolve(NULL)
#9 /root/.shared_extensions/phpactor2/vendor/amphp/amp/lib/Loop/NativeDriver.php(142): Amp\Delayed->Amp\{closure}('drwm', NULL)
#10 /root/.shared_extensions/phpactor2/vendor/amphp/amp/lib/Loop/Driver.php(138): Amp\Loop\NativeDriver->dispatch(true)
#11 /root/.shared_extensions/phpactor2/vendor/amphp/amp/lib/Loop/Driver.php(72): Amp\Loop\Driver->tick()
#12 /root/.shared_extensions/phpactor2/vendor/amphp/amp/lib/Loop.php(95): Amp\Loop\Driver->run()
#13 /root/.shared_extensions/phpactor2/vendor/phpactor/language-server/lib/Core/Server/LanguageServer.php(112): Amp\Loop::run(Object(Closure))
#14 /root/.shared_extensions/phpactor2/lib/Extension/LanguageServer/Command/StartCommand.php(54): Phpactor\LanguageServer\Core\Server\LanguageServer->run()
#15 /root/.shared_extensions/phpactor2/vendor/symfony/console/Command/Command.php(298): Phpactor\Extension\LanguageServer\Command\StartCommand->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#16 /root/.shared_extensions/phpactor2/vendor/symfony/console/Application.php(1024): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#17 /root/.shared_extensions/phpactor2/vendor/symfony/console/Application.php(299): Symfony\Component\Console\Application->doRunCommand(Object(Phpactor\Extension\LanguageServer\Command\StartCommand), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#18 /root/.shared_extensions/phpactor2/lib/Application.php(48): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#19 /root/.shared_extensions/phpactor2/vendor/symfony/console/Application.php(171): Phpactor\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#20 /root/.shared_extensions/phpactor2/bin/phpactor(47): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#21 {main}
```

Pull request:
Fix type issue
Fixes #1778 

Patch:
diff --git a/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseExtractMethod.php b/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseExtractMethod.php
index 91c82ae2..37f444d2 100644
--- a/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseExtractMethod.php
+++ b/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseExtractMethod.php
@@ -99,7 +99,9 @@ class WorseExtractMethod implements ExtractMethod
             }
             
             $startNode = $stmt ?? $startNode;
-            assert($startNode instanceof Node);
+            if (!$startNode instanceof Node) {
+                return false;
+            }
         }
 
         while ($startNode->parent && !($startNode->parent instanceof CompoundStatementNode)) {
diff --git a/lib/Extension/LanguageServerCodeTransform/Model/NameImport/CandidateFinder.php b/lib/Extension/LanguageServerCodeTransform/Model/NameImport/CandidateFinder.php
index 04892a1b..130b4695 100644
--- a/lib/Extension/LanguageServerCodeTransform/Model/NameImport/CandidateFinder.php
+++ b/lib/Extension/LanguageServerCodeTransform/Model/NameImport/CandidateFinder.php
@@ -31,8 +31,8 @@ class CandidateFinder
     {
         $diagnostics = $this->reflector->diagnostics($item->text)->byClass(UnresolvableNameDiagnostic::class);
 
-        return new NameWithByteOffsets(...array_map(function (UnresolvableNameDiagnostic $diagnostic) {
-            return new NameWithByteOffset($diagnostic->name(), $diagnostic->range()->start());
+        return new NameWithByteOffsets(...array_map(function (UnresolvableNameDiagnostic $diagnostic): NameWithByteOffset {
+            return new NameWithByteOffset($diagnostic->name(), $diagnostic->range()->start(), $diagnostic->type());
         }, iterator_to_array($diagnostics)));
     }
 
diff --git a/lib/WorseReflection/Bridge/TolerantParser/Diagnostics/UnresolvableNameProvider.php b/lib/WorseReflection/Bridge/TolerantParser/Diagnostics/UnresolvableNameProvider.php
index 761f193c..50b04c7c 100644
--- a/lib/WorseReflection/Bridge/TolerantParser/Diagnostics/UnresolvableNameProvider.php
+++ b/lib/WorseReflection/Bridge/TolerantParser/Diagnostics/UnresolvableNameProvider.php
@@ -7,7 +7,6 @@ use Microsoft\PhpParser\Node\Attribute;
 use Microsoft\PhpParser\Node\ClassBaseClause;
 use Microsoft\PhpParser\Node\DelimitedList\QualifiedNameList;
 use Microsoft\PhpParser\Node\MethodDeclaration;
-use Microsoft\PhpParser\Node\Parameter;
 use Microsoft\PhpParser\Node\Expression\ScopedPropertyAccessExpression;
 use Microsoft\PhpParser\Node\Statement\FunctionDeclaration;
 use Microsoft\PhpParser\Node\Expression\ObjectCreationExpression;
@@ -15,7 +14,6 @@ use Microsoft\PhpParser\Node\Expression\CallExpression;
 use Microsoft\PhpParser\Node\QualifiedName;
 use Microsoft\PhpParser\ResolvedName;
 use Phpactor\Name\FullyQualifiedName as PhpactorFullyQualifiedName;
-use Phpactor\Name\Name;
 use Phpactor\TextDocument\ByteOffsetRange;
 use Phpactor\WorseReflection\Bridge\TolerantParser\Patch\TolerantQualifiedNameResolver;
 use Phpactor\WorseReflection\Core\DiagnosticProvider;
@@ -92,7 +90,6 @@ class UnresolvableNameProvider implements DiagnosticProvider
             !$name->parent instanceof ScopedPropertyAccessExpression &&
             !$name->parent instanceof FunctionDeclaration &&
             !$name->parent instanceof MethodDeclaration &&
-            !$name->parent instanceof Parameter &&
             !$name->parent instanceof Attribute
         ) {
             return;
diff --git a/lib/WorseReflection/Bridge/TolerantParser/Reflection/ReflectionEnum.php b/lib/WorseReflection/Bridge/TolerantParser/Reflection/ReflectionEnum.php
index 110e3c81..c97aa624 100644
--- a/lib/WorseReflection/Bridge/TolerantParser/Reflection/ReflectionEnum.php
+++ b/lib/WorseReflection/Bridge/TolerantParser/Reflection/ReflectionEnum.php
@@ -4,6 +4,7 @@ namespace Phpactor\WorseReflection\Bridge\TolerantParser\Reflection;
 
 use Microsoft\PhpParser\Node;
 use Microsoft\PhpParser\Node\Statement\EnumDeclaration;
+use Phpactor\WorseReflection\Core\Exception\NotFound;
 use Phpactor\WorseReflection\Core\Reflection\Collection\ReflectionEnumCaseCollection as PhpactorReflectionEnumCaseCollection;
 use Phpactor\WorseReflection\Core\Reflection\Collection\ReflectionMethodCollection;
 use Phpactor\WorseReflection\Core\Reflection\Collection\ReflectionPropertyCollection;
@@ -42,7 +43,13 @@ class ReflectionEnum extends AbstractReflectionClass implements CoreReflectionEn
     public function methods(ReflectionClassLike $contextClass = null): CoreReflectionMethodCollection
     {
         $contextClass = $contextClass ?: $this;
-        return ReflectionMethodCollection::fromEnumDeclaration($this->serviceLocator, $this->node, $contextClass);
+        $methods = ReflectionMethodCollection::fromEnumDeclaration($this->serviceLocator, $this->node, $contextClass);
+        try {
+            $enumMethods = $this->serviceLocator()->reflector()->reflectInterface('BackedEnum')->methods($this);
+            return $enumMethods->merge($methods);
+        } catch (NotFound $notFound) {
+            return $methods;
+        }
     }
 
     public function cases(): ReflectionEnumCaseCollection
diff --git a/lib/WorseReflection/Core/Inference/Resolver/ArrayCreationExpressionResolver.php b/lib/WorseReflection/Core/Inference/Resolver/ArrayCreationExpressionResolver.php
index 8bcc0a49..1b2c2cc9 100644
--- a/lib/WorseReflection/Core/Inference/Resolver/ArrayCreationExpressionResolver.php
+++ b/lib/WorseReflection/Core/Inference/Resolver/ArrayCreationExpressionResolver.php
@@ -35,7 +35,11 @@ class ArrayCreationExpressionResolver implements Resolver
             $value = $resolver->resolveNode($frame, $element->elementValue)->type();
             if ($element->elementKey) {
                 $key = $resolver->resolveNode($frame, $element->elementKey)->type();
-                $array[TypeUtil::valueOrNull($key)] = $value;
+                $keyValue = TypeUtil::valueOrNull($key);
+                if (null === $keyValue) {
+                    continue;
+                }
+                $array[$keyValue] = $value;
                 continue;
             }
 


Test patch:
diff --git a/lib/Extension/LanguageServerCodeTransform/Tests/Unit/CodeAction/ImportNameProviderTest.php b/lib/Extension/LanguageServerCodeTransform/Tests/Unit/CodeAction/ImportNameProviderTest.php
index 758278de..3805107b 100644
--- a/lib/Extension/LanguageServerCodeTransform/Tests/Unit/CodeAction/ImportNameProviderTest.php
+++ b/lib/Extension/LanguageServerCodeTransform/Tests/Unit/CodeAction/ImportNameProviderTest.php
@@ -2,12 +2,12 @@
 
 namespace Phpactor\Extension\LanguageServerCodeTransform\Tests\Unit\CodeAction;
 
+use Closure;
 use Generator;
 use Phpactor\Extension\LanguageServerBridge\Converter\PositionConverter;
 use Phpactor\Extension\LanguageServerCodeTransform\LanguageServerCodeTransformExtension;
 use Phpactor\Extension\LanguageServerCodeTransform\Tests\IntegrationTestCase;
 use Phpactor\Extension\WorseReflection\WorseReflectionExtension;
-use Phpactor\LanguageServerProtocol\CodeAction;
 use Phpactor\LanguageServerProtocol\CodeActionContext;
 use Phpactor\LanguageServerProtocol\CodeActionParams;
 use Phpactor\LanguageServerProtocol\CodeActionRequest;
@@ -25,7 +25,7 @@ class ImportNameProviderTest extends IntegrationTestCase
      * @dataProvider provideImportProvider
      * @group flakey
      */
-    public function testImportProvider(string $manifest, int $expectedCount, int $expectedDiagnosticCount, bool $imprtGlobals = false): void
+    public function testImportProvider(string $manifest, Closure $assertion, bool $imprtGlobals = false): void
     {
         $this->workspace()->reset();
         $this->workspace()->loadManifest($manifest);
@@ -60,18 +60,12 @@ class ImportNameProviderTest extends IntegrationTestCase
             ),
             new CodeActionContext([])
         ));
-
         $tester->assertSuccess($result);
 
-        self::assertCount($expectedCount, array_filter($result->result, function (CodeAction $action) {
-            return $action->kind == 'quickfix.import_class';
-        }), 'Number of code actions');
-        $tester->textDocument()->save('file:///foobar');
-
         $diagnostics = $tester->transmitter()->filterByMethod('textDocument/publishDiagnostics')->shiftNotification();
         self::assertNotNull($diagnostics);
         $diagnostics = $diagnostics->params['diagnostics'];
-        self::assertEquals($expectedDiagnosticCount, count($diagnostics), 'Number of diagnostics');
+        $assertion($result->result, $diagnostics);
     }
 
     /**
@@ -95,7 +89,11 @@ class ImportNameProviderTest extends IntegrationTestCase
                 // File: subject.php
                 <?php new MissingNameFoo();'
                 EOT
-            , 0, 1
+            , function (array $codeActions, array $diagnostics): void {
+                self::assertCount(0, $codeActions);
+                self::assertCount(1, $diagnostics);
+            }
+
         ];
 
         yield 'code actions + diagnostic for namespaced non-existant class' => [
@@ -103,7 +101,10 @@ class ImportNameProviderTest extends IntegrationTestCase
                 // File: subject.php
                 <?php namespace Bar; new MissingNameFoo();'
                 EOT
-            , 0, 1
+            , function (array $codeActions, array $diagnostics): void {
+                self::assertCount(0, $codeActions);
+                self::assertCount(1, $diagnostics);
+            }
         ];
 
         yield 'code action and diagnostic for missing global class name' => [
@@ -113,7 +114,10 @@ class ImportNameProviderTest extends IntegrationTestCase
                 // File: Generator.php
                 <?php class Generator {}
                 EOT
-            , 1, 1, false
+            , function (array $codeActions, array $diagnostics): void {
+                self::assertCount(1, $codeActions);
+                self::assertCount(1, $diagnostics);
+            }, false
         ];
 
         yield 'no code action or diagnostics for missing global function name' => [
@@ -122,7 +126,10 @@ class ImportNameProviderTest extends IntegrationTestCase
                 <?php namespace Foobar;
                 sprintf('foo %s', 'bar');
                 EOT
-            , 0, 0
+            , function (array $codeActions, array $diagnostics): void {
+                self::assertCount(0, $codeActions);
+                self::assertCount(0, $diagnostics);
+            }, false
         ];
 
         yield 'no diagnostics for class declared in same namespace' => [
@@ -144,7 +151,10 @@ class ImportNameProviderTest extends IntegrationTestCase
                 {
                 }
                 EOT
-            , 0, 0
+            , function (array $codeActions, array $diagnostics): void {
+                self::assertCount(0, $codeActions);
+                self::assertCount(0, $diagnostics);
+            }
         ];
 
         yield 'built in global funtion' => [
@@ -156,10 +166,13 @@ class ImportNameProviderTest extends IntegrationTestCase
 
                 sprintf('foo', 'bar');
                 EOT
-            , 0, 0
+            , function (array $codeActions, array $diagnostics): void {
+                self::assertCount(0, $codeActions);
+                self::assertCount(0, $diagnostics);
+            }
         ];
 
-        yield 'built in global funtion with import globals' => [
+        yield 'built in global function with import globals' => [
             <<<'EOT'
                 // File: functions.php
                 // File: subject.php
@@ -170,7 +183,13 @@ class ImportNameProviderTest extends IntegrationTestCase
                 $bar = [];
                 explode(array_keys($bar));
                 EOT
-            , 2, 2, true
+            , function (array $codeActions, array $diagnostics): void {
+                self::assertCount(3, $codeActions);
+                self::assertEquals('Import function "explode"', $codeActions[0]->title);
+                self::assertEquals('Import function "array_keys"', $codeActions[1]->title);
+                self::assertEquals('Import all unresolved names', $codeActions[2]->title);
+                self::assertCount(2, $diagnostics);
+            }, true
         ];
 
         yield 'constant' => [
@@ -183,7 +202,10 @@ class ImportNameProviderTest extends IntegrationTestCase
                 if (INF) {
                 }
                 EOT
-            , 0, 0, true
+            , function (array $codeActions, array $diagnostics): void {
+                self::assertCount(0, $codeActions);
+                self::assertCount(0, $diagnostics);
+            }, true
         ];
     }
 }
diff --git a/lib/WorseReflection/Tests/Inference/array-creation-expression/test1.test b/lib/WorseReflection/Tests/Inference/array-creation-expression/test1.test
new file mode 100644
index 00000000..e69de29b
diff --git a/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Diagonstics/UnresolvableNameProvider/ConstantsParam.test b/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Diagonstics/UnresolvableNameProvider/ConstantsParam.test
new file mode 100644
index 00000000..ccfa3f70
--- /dev/null
+++ b/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Diagonstics/UnresolvableNameProvider/ConstantsParam.test
@@ -0,0 +1,9 @@
+<?php
+
+class RpcCommand
+{
+    public function __construct(
+        $inputStream = STDIN
+    ) {
+    }
+}
diff --git a/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Diagonstics/UnresolvableNameProvider/Parameter.test b/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Diagonstics/UnresolvableNameProvider/Parameter.test
new file mode 100644
index 00000000..d202f39e
--- /dev/null
+++ b/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Diagonstics/UnresolvableNameProvider/Parameter.test
@@ -0,0 +1,9 @@
+<?php
+
+class RpcCommand
+{
+    public function __construct(
+        $inputStream = Foo::BAR
+    ) {
+    }
+}
diff --git a/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Diagonstics/UnresolvableNameProviderTest.php b/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Diagonstics/UnresolvableNameProviderTest.php
index 6fba18cc..08b1b549 100644
--- a/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Diagonstics/UnresolvableNameProviderTest.php
+++ b/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Diagonstics/UnresolvableNameProviderTest.php
@@ -47,6 +47,17 @@ class UnresolvableNameProviderTest extends DiagnosticsTestCase
         self::assertCount(1, $diagnostics);
     }
 
+    public function checkConstantsParam(Diagnostics $diagnostics): void
+    {
+        self::assertCount(0, $diagnostics);
+    }
+
+    public function checkParameter(Diagnostics $diagnostics): void
+    {
+        self::assertCount(1, $diagnostics);
+        self::assertEquals('Class "Foo" not found', $diagnostics->at(0)->message());
+    }
+
     /**
      * @dataProvider provideReturnsUnresolableFunctions
      * @dataProvider provideReturnsUnresolableClass
diff --git a/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionEnumTest.php b/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionEnumTest.php
index 0338e8b4..c8ee966d 100644
--- a/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionEnumTest.php
+++ b/lib/WorseReflection/Tests/Integration/Bridge/TolerantParser/Reflection/ReflectionEnumTest.php
@@ -117,5 +117,27 @@ class ReflectionEnumTest extends IntegrationTestCase
                 self::assertEquals('FOO', $case->value());
             },
         ];
+        yield 'Return backed methods' => [
+        <<<'EOT'
+                            <?php
+
+                            interface BackedEnum {
+                                public static function from(int|string $value): static;
+                                public static function tryFrom(int|string $value): ?static;
+                                public static function UnitEnum::cases(): array;
+                            }
+
+                            enum Enum1:string
+                            {
+                            }
+
+            EOT
+            ,
+            'Enum1',
+            function (ReflectionEnum $class): void {
+                $method = $class->methods()->get('from');
+                self::assertEquals('Enum1', $method->returnType()->__toString());
+            },
+        ];
     }
 }