Repository: phpactor/phpactor (php)

Issue:
enum: Fill Match Arms
```
            $css[] = match ($layout->direction) {
                Direction::Vertical => null,
                Direction::Horizontal => null,
            }
```

Pull request description:
Gh 2401 fill match arms
Fixes #2401

- Adds code action for generating match arms
- Refactors to have a `ByteRangeRefactor` interface which can use the same provider for all implementations
- Fixes the unit enum resolution type

TODO:

- Make some utility for fixing whitespace...
- Refactor any other classes that can fit into the ByteRangeRefactor interface.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b1e8ede0..19cfa3bd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ Bug fixes:
 Features:
 
   - Generate enum cases and class constants #2422
+  - Generate enum match arms #2401
 
 Improvements:
 
diff --git a/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseFillMatchArms.php b/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseFillMatchArms.php
new file mode 100644
index 00000000..f3c87c44
--- /dev/null
+++ b/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseFillMatchArms.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace Phpactor\CodeTransform\Adapter\WorseReflection\Refactor;
+
+use Microsoft\PhpParser\MissingToken;
+use Microsoft\PhpParser\Node\Expression\MatchExpression;
+use Microsoft\PhpParser\Node\Expression\ScopedPropertyAccessExpression;
+use Microsoft\PhpParser\Node\MatchArm;
+use Microsoft\PhpParser\Parser;
+use Phpactor\CodeTransform\Domain\Refactor\ByteOffsetRefactor;
+use Phpactor\TextDocument\ByteOffset;
+use Phpactor\TextDocument\TextDocument;
+use Phpactor\TextDocument\TextEdit;
+use Phpactor\TextDocument\TextEdits;
+use Phpactor\TextDocument\Util\LineAtOffset;
+use Phpactor\WorseReflection\Bridge\TolerantParser\Reflection\ReflectionMatchExpression;
+use Phpactor\WorseReflection\Core\Exception\NotFound;
+use Phpactor\WorseReflection\Core\Reflection\ReflectionEnum;
+use Phpactor\WorseReflection\Core\Type\ReflectedClassType;
+use Phpactor\WorseReflection\Core\Util\NodeUtil;
+use Phpactor\WorseReflection\Reflector;
+
+final class WorseFillMatchArms implements ByteOffsetRefactor
+{
+    public function __construct(
+        private Reflector $reflector,
+        private Parser $parser,
+    ) {
+    }
+
+    public function refactor(TextDocument $document, ByteOffset $offset): TextEdits
+    {
+        $node = $this->parser->parseSourceFile($document->__toString())->getDescendantNodeAtPosition($offset->toInt());
+        $node = $node instanceof MatchExpression ? $node : $node->getFirstAncestor(MatchExpression::class);
+        if (!$node instanceof MatchExpression) {
+            return TextEdits::none();
+        }
+        try {
+            $reflectionNode = $this->reflector->reflectNode($document, $node->getStartPosition());
+        } catch (NotFound $notFound) {
+            return TextEdits::none();
+        }
+
+        if (!$reflectionNode instanceof ReflectionMatchExpression) {
+            return TextEdits::none();
+        }
+
+        $type = $reflectionNode->expressionType();
+        if (!$type instanceof ReflectedClassType) {
+            return TextEdits::none();
+        }
+
+        $enum = $type->reflectionOrNull();
+        if (!$enum instanceof ReflectionEnum) {
+            return TextEdits::none();
+        }
+
+        $edits = [];
+        [$prefix, $whitespace, $postfix, $start, $existingCases] = $this->existingCases($node);
+        if ($prefix) {
+            $edits[] = TextEdit::create($start, 0, $prefix);
+        }
+        $edits[] = TextEdit::create($start, 0, "\n");
+        foreach ($enum->cases() as $case) {
+            if (in_array($case->name(), $existingCases)) {
+                continue;
+            }
+            $edits[] = TextEdit::create($start, 0, sprintf("%s%s::%s => null,\n", $whitespace, $enum->name()->short(), $case->name()));
+        }
+        $edits[] = TextEdit::create($start, 0, $whitespace);
+        if ($postfix) {
+            $edits[] = TextEdit::create($start, 0, $postfix);
+        }
+
+        return TextEdits::fromTextEdits($edits);
+    }
+
+    /**
+     * @return array{?string,string,?string,int,string[]}
+     */
+    private function existingCases(MatchExpression $node): array
+    {
+        $start = $node->openBrace->getStartPosition() + 1;
+        $prefix = null;
+        $postfix = null;
+        $whitespace = '';
+        if ($node->openBrace instanceof MissingToken) {
+            $prefix = "{\n";
+        }
+        if ($node->closeBrace instanceof MissingToken) {
+            $postfix = '}';
+        }
+        $cases = [];
+        foreach ($node->arms?->getChildNodes() ?? [] as $arm) {
+            assert($arm instanceof MatchArm);
+            $start = $arm->getEndPosition() + 1;
+            foreach ($arm->conditionList->getChildNodes() as $node) {
+                if (!$node instanceof ScopedPropertyAccessExpression) {
+                    continue;
+                }
+                $cases[] = NodeUtil::nameFromTokenOrNode($node, $node->memberName);
+
+            }
+        }
+        $line = LineAtOffset::lineAtByteOffset($node->getFileContents(), ByteOffset::fromInt($start));
+        if (preg_match('{^\s+}', $line, $matches)) {
+            $whitespace = $matches[0];
+        }
+        return [$prefix, $whitespace, $postfix, $start, $cases];
+    }
+}
diff --git a/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseFillObject.php b/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseFillObject.php
index 0dfeaead..73415d02 100644
--- a/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseFillObject.php
+++ b/lib/CodeTransform/Adapter/WorseReflection/Refactor/WorseFillObject.php
@@ -8,7 +8,7 @@ use Phpactor\CodeBuilder\Domain\Builder\SourceCodeBuilder;
 use Phpactor\CodeBuilder\Domain\Code;
 use Phpactor\CodeBuilder\Domain\Updater;
 use Phpactor\CodeTransform\Adapter\WorseReflection\Helper\EmptyValueRenderer;
-use Phpactor\CodeTransform\Domain\Refactor\FillObject;
+use Phpactor\CodeTransform\Domain\Refactor\ByteOffsetRefactor;
 use Phpactor\TextDocument\ByteOffset;
 use Phpactor\TextDocument\TextDocument;
 use Phpactor\TextDocument\TextEdit;
@@ -21,7 +21,7 @@ use Phpactor\WorseReflection\Core\Type\HasEmptyType;
 use Phpactor\WorseReflection\Core\Type\ReflectedClassType;
 use Phpactor\WorseReflection\Reflector;
 
-class WorseFillObject implements FillObject
+class WorseFillObject implements ByteOffsetRefactor
 {
     private EmptyValueRenderer $valueRenderer;
 
@@ -35,7 +35,7 @@ class WorseFillObject implements FillObject
         $this->valueRenderer = new EmptyValueRenderer();
     }
 
-    public function fillObject(TextDocument $document, ByteOffset $offset): TextEdits
+    public function refactor(TextDocument $document, ByteOffset $offset): TextEdits
     {
         $node = $this->parser->parseSourceFile($document->__toString())->getDescendantNodeAtPosition($offset->toInt());
         $node = $node->getFirstAncestor(ObjectCreationExpression::class);
diff --git a/lib/CodeTransform/Domain/Refactor/FillObject.php b/lib/CodeTransform/Domain/Refactor/ByteOffsetRefactor.php
similarity index 60%
rename from lib/CodeTransform/Domain/Refactor/FillObject.php
rename to lib/CodeTransform/Domain/Refactor/ByteOffsetRefactor.php
index 5f536aad..901f4391 100644
--- a/lib/CodeTransform/Domain/Refactor/FillObject.php
+++ b/lib/CodeTransform/Domain/Refactor/ByteOffsetRefactor.php
@@ -6,7 +6,7 @@ use Phpactor\TextDocument\ByteOffset;
 use Phpactor\TextDocument\TextDocument;
 use Phpactor\TextDocument\TextEdits;
 
-interface FillObject
+interface ByteOffsetRefactor
 {
-    public function fillObject(TextDocument $document, ByteOffset $offset): TextEdits;
+    public function refactor(TextDocument $document, ByteOffset $offset): TextEdits;
 }
diff --git a/lib/Extension/CodeTransform/CodeTransformExtension.php b/lib/Extension/CodeTransform/CodeTransformExtension.php
index a2e50b9d..db357dd7 100644
--- a/lib/Extension/CodeTransform/CodeTransformExtension.php
+++ b/lib/Extension/CodeTransform/CodeTransformExtension.php
@@ -2,6 +2,7 @@
 
 namespace Phpactor\Extension\CodeTransform;
 
+use Microsoft\PhpParser\Parser;
 use Phpactor\CodeBuilder\Adapter\WorseReflection\TypeRenderer\WorseTypeRenderer;
 use Phpactor\CodeBuilder\Adapter\WorseReflection\TypeRenderer\WorseTypeRenderer74;
 use Phpactor\CodeBuilder\Adapter\WorseReflection\TypeRenderer\WorseTypeRenderer80;
@@ -26,6 +27,7 @@ use Phpactor\CodeTransform\Adapter\WorseReflection\GenerateFromExisting\Interfac
 use Phpactor\CodeTransform\Adapter\TolerantParser\Refactor\TolerantRenameVariable;
 use Phpactor\CodeTransform\Adapter\WorseReflection\Helper\WorseMissingMemberFinder;
 use Phpactor\CodeTransform\Adapter\WorseReflection\Refactor\WorseExtractMethod;
+use Phpactor\CodeTransform\Adapter\WorseReflection\Refactor\WorseFillMatchArms;
 use Phpactor\CodeTransform\Adapter\WorseReflection\Refactor\WorseFillObject;
 use Phpactor\CodeTransform\Adapter\WorseReflection\Refactor\WorseGenerateConstructor;
 use Phpactor\CodeTransform\Adapter\WorseReflection\Refactor\WorseGenerateMutator;
@@ -53,7 +55,6 @@ use Phpactor\CodeTransform\Domain\Refactor\ChangeVisiblity;
 use Phpactor\CodeTransform\Domain\Refactor\ExtractConstant;
 use Phpactor\CodeTransform\Domain\Refactor\ExtractExpression;
 use Phpactor\CodeTransform\Domain\Refactor\ExtractMethod;
-use Phpactor\CodeTransform\Domain\Refactor\FillObject;
 use Phpactor\CodeTransform\Domain\Refactor\GenerateConstructor;
 use Phpactor\CodeTransform\Domain\Refactor\GenerateDecorator;
 use Phpactor\CodeTransform\Domain\Refactor\ImportName;
@@ -283,15 +284,21 @@ class CodeTransformExtension implements Extension
                 $container->parameter(self::PARAM_IMPORT_GLOBALS)->bool(),
             );
         });
-        $container->register(FillObject::class, function (Container $container) {
+        $container->register(WorseFillObject::class, function (Container $container) {
             return new WorseFillObject(
                 $container->expect(WorseReflectionExtension::SERVICE_REFLECTOR, Reflector::class),
-                $container->get(WorseReflectionExtension::SERVICE_PARSER),
+                $container->expect(WorseReflectionExtension::SERVICE_PARSER, Parser::class),
                 $container->get(Updater::class),
                 $container->parameter(self::PARAM_OBJECT_FILL_NAMED)->bool(),
                 $container->parameter(self::PARAM_OBJECT_FILL_HINT)->bool(),
             );
         });
+        $container->register(WorseFillMatchArms::class, function (Container $container) {
+            return new WorseFillMatchArms(
+                $container->expect(WorseReflectionExtension::SERVICE_REFLECTOR, Reflector::class),
+                $container->expect(WorseReflectionExtension::SERVICE_PARSER, Parser::class),
+            );
+        });
 
         $container->register(GenerateConstructor::class, function (Container $container) {
             return new WorseGenerateConstructor(
diff --git a/lib/Extension/LanguageServerCodeTransform/CodeAction/FillObjectProvider.php b/lib/Extension/LanguageServerCodeTransform/CodeAction/ByteOffsetRefactorProvider.php
similarity index 74%
rename from lib/Extension/LanguageServerCodeTransform/CodeAction/FillObjectProvider.php
rename to lib/Extension/LanguageServerCodeTransform/CodeAction/ByteOffsetRefactorProvider.php
index 2ae7184c..ce461012 100644
--- a/lib/Extension/LanguageServerCodeTransform/CodeAction/FillObjectProvider.php
+++ b/lib/Extension/LanguageServerCodeTransform/CodeAction/ByteOffsetRefactorProvider.php
@@ -5,7 +5,7 @@ namespace Phpactor\Extension\LanguageServerCodeTransform\CodeAction;
 use Amp\CancellationToken;
 use Amp\Promise;
 use Amp\Success;
-use Phpactor\CodeTransform\Domain\Refactor\FillObject;
+use Phpactor\CodeTransform\Domain\Refactor\ByteOffsetRefactor;
 use Phpactor\Extension\LanguageServerBridge\Converter\RangeConverter;
 use Phpactor\Extension\LanguageServerBridge\Converter\TextDocumentConverter;
 use Phpactor\Extension\LanguageServerBridge\Converter\TextEditConverter;
@@ -15,17 +15,19 @@ use Phpactor\LanguageServerProtocol\TextDocumentItem;
 use Phpactor\LanguageServerProtocol\WorkspaceEdit;
 use Phpactor\LanguageServer\Core\CodeAction\CodeActionProvider;
 
-class FillObjectProvider implements CodeActionProvider
+class ByteOffsetRefactorProvider implements CodeActionProvider
 {
-    const KIND = 'quickfix.fill.object';
-
-    public function __construct(private FillObject $fillObject)
-    {
+    public function __construct(
+        private ByteOffsetRefactor $refactor,
+        private string $kind,
+        private string $title,
+        private string $description,
+    ) {
     }
 
     public function provideActionsFor(TextDocumentItem $textDocument, Range $range, CancellationToken $cancel): Promise
     {
-        $edits = $this->fillObject->fillObject(
+        $edits = $this->refactor->refactor(
             TextDocumentConverter::fromLspTextItem($textDocument),
             RangeConverter::toPhpactorRange($range, $textDocument->text)->start()
         );
@@ -36,8 +38,8 @@ class FillObjectProvider implements CodeActionProvider
 
         return new Success([
             new CodeAction(
-                title: 'Fill object',
-                kind: self::KIND,
+                title: $this->title,
+                kind: $this->kind,
                 diagnostics: [],
                 isPreferred: false,
                 edit: new WorkspaceEdit([
@@ -49,10 +51,10 @@ class FillObjectProvider implements CodeActionProvider
 
     public function kinds(): array
     {
-        return [self::KIND];
+        return [$this->kind];
     }
     public function describe(): string
     {
-        return 'fill new object construct with named parameters';
+        return $this->description;
     }
 }
diff --git a/lib/Extension/LanguageServerCodeTransform/LanguageServerCodeTransformExtension.php b/lib/Extension/LanguageServerCodeTransform/LanguageServerCodeTransformExtension.php
index 571f4452..f4a0f6c7 100644
--- a/lib/Extension/LanguageServerCodeTransform/LanguageServerCodeTransformExtension.php
+++ b/lib/Extension/LanguageServerCodeTransform/LanguageServerCodeTransformExtension.php
@@ -3,6 +3,8 @@
 namespace Phpactor\Extension\LanguageServerCodeTransform;
 
 use Phpactor\ClassFileConverter\Domain\FileToClass;
+use Phpactor\CodeTransform\Adapter\WorseReflection\Refactor\WorseFillMatchArms;
+use Phpactor\CodeTransform\Adapter\WorseReflection\Refactor\WorseFillObject;
 use Phpactor\CodeTransform\Domain\Generators;
 use Phpactor\CodeTransform\Domain\Helper\MissingMemberFinder;
 use Phpactor\CodeTransform\Domain\Refactor\PropertyAccessGenerator;
@@ -10,7 +12,6 @@ use Phpactor\CodeTransform\Domain\Refactor\ReplaceQualifierWithImport;
 use Phpactor\CodeTransform\Domain\Refactor\ExtractConstant;
 use Phpactor\CodeTransform\Domain\Refactor\ExtractExpression;
 use Phpactor\CodeTransform\Domain\Refactor\ExtractMethod;
-use Phpactor\CodeTransform\Domain\Refactor\FillObject;
 use Phpactor\CodeTransform\Domain\Refactor\GenerateConstructor;
 use Phpactor\CodeTransform\Domain\Refactor\GenerateDecorator;
 use Phpactor\CodeTransform\Domain\Refactor\GenerateMember;
@@ -27,7 +28,7 @@ use Phpactor\Extension\LanguageServerCodeTransform\CodeAction\CreateUnresolvable
 use Phpactor\Extension\LanguageServerCodeTransform\CodeAction\ExtractConstantProvider;
 use Phpactor\Extension\LanguageServerCodeTransform\CodeAction\ExtractExpressionProvider;
 use Phpactor\Extension\LanguageServerCodeTransform\CodeAction\ExtractMethodProvider;
-use Phpactor\Extension\LanguageServerCodeTransform\CodeAction\FillObjectProvider;
+use Phpactor\Extension\LanguageServerCodeTransform\CodeAction\ByteOffsetRefactorProvider;
 use Phpactor\Extension\LanguageServerCodeTransform\CodeAction\CorrectUndefinedVariableCodeAction;
 use Phpactor\Extension\LanguageServerCodeTransform\CodeAction\GenerateConstructorProvider;
 use Phpactor\Extension\LanguageServerCodeTransform\CodeAction\GenerateDecoratorProvider;
@@ -460,9 +461,22 @@ class LanguageServerCodeTransformExtension implements Extension
             LanguageServerExtension::TAG_CODE_ACTION_PROVIDER => []
         ]);
 
-        $container->register(FillObjectProvider::class, function (Container $container) {
-            return new FillObjectProvider(
-                $container->get(FillObject::class)
+        $container->register(ByteOffsetRefactorProvider::class.'.fill_object', function (Container $container) {
+            return new ByteOffsetRefactorProvider(
+                $container->get(WorseFillObject::class),
+                'quickfix.fill.object',
+                'Fill object',
+                'fill new object construct with named parameters',
+            );
+        }, [
+            LanguageServerExtension::TAG_CODE_ACTION_PROVIDER => []
+        ]);
+        $container->register(ByteOffsetRefactorProvider::class.'.fill_match_arms', function (Container $container) {
+            return new ByteOffsetRefactorProvider(
+                $container->get(WorseFillMatchArms::class),
+                'quickfix.fill.matchArms',
+                'Fill match arms',
+                'fill missing match arms for an enum',
             );
         }, [
             LanguageServerExtension::TAG_CODE_ACTION_PROVIDER => []
diff --git a/lib/WorseReflection/Bridge/TolerantParser/Reflection/ReflectionMatchExpression.php b/lib/WorseReflection/Bridge/TolerantParser/Reflection/ReflectionMatchExpression.php
new file mode 100644
index 00000000..b17f8a61
--- /dev/null
+++ b/lib/WorseReflection/Bridge/TolerantParser/Reflection/ReflectionMatchExpression.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Phpactor\WorseReflection\Bridge\TolerantParser\Reflection;
+
+use Microsoft\PhpParser\Node;
+use Microsoft\PhpParser\Node\Expression\MatchExpression;
+use Phpactor\TextDocument\ByteOffsetRange;
+use Phpactor\WorseReflection\Core\Inference\Frame;
+use Phpactor\WorseReflection\Core\Reflection\ReflectionNode;
+use Phpactor\WorseReflection\Core\ServiceLocator;
+use Phpactor\WorseReflection\Core\Type;
+use Phpactor\WorseReflection\Core\TypeFactory;
+
+class ReflectionMatchExpression implements ReflectionNode
+{
+    /**
+     * @param MatchExpression $node
+     */
+    public function __construct(
+        private ServiceLocator $services,
+        private Frame $frame,
+        private Node $node
+    ) {
+    }
+
+    public function position(): ByteOffsetRange
+    {
+        return ByteOffsetRange::fromInts(
+            $this->node->getStartPosition(),
+            $this->node->getEndPosition()
+        );
+    }
+
+    public function expressionType(): Type
+    {
+        if ($this->node->expression === null) {
+            return TypeFactory::unknown();
+        }
+        $expr = $this->services->nodeContextResolver()->resolveNode($this->frame, $this->node->expression);
+        return $expr->type();
+    }
+    public function scope(): ReflectionScope
+    {
+        return new ReflectionScope($this->services->reflector(), $this->node);
+    }
+}
diff --git a/lib/WorseReflection/Core/Inference/NodeReflector.php b/lib/WorseReflection/Core/Inference/NodeReflector.php
index 518064f0..9e783865 100644
--- a/lib/WorseReflection/Core/Inference/NodeReflector.php
+++ b/lib/WorseReflection/Core/Inference/NodeReflector.php
@@ -5,9 +5,11 @@ namespace Phpactor\WorseReflection\Core\Inference;
 use Microsoft\PhpParser\Node;
 use Microsoft\PhpParser\Node\Attribute;
 use Microsoft\PhpParser\Node\Expression\CallExpression;
+use Microsoft\PhpParser\Node\Expression\MatchExpression;
 use Microsoft\PhpParser\Node\Expression\MemberAccessExpression;
 use Microsoft\PhpParser\Node\Expression\ObjectCreationExpression;
 use Phpactor\WorseReflection\Bridge\TolerantParser\Reflection\ReflectionAttribute;
+use Phpactor\WorseReflection\Bridge\TolerantParser\Reflection\ReflectionMatchExpression;
 use Phpactor\WorseReflection\Bridge\TolerantParser\Reflection\ReflectionMethodCall;
 use Phpactor\WorseReflection\Bridge\TolerantParser\Reflection\ReflectionObjectCreationExpression as PhpactorReflectionObjectCreationExpression;
 use Phpactor\WorseReflection\Bridge\TolerantParser\Reflection\ReflectionStaticMemberAccess;
@@ -38,6 +40,10 @@ class NodeReflector
             return $this->reflectObjectCreationExpression($frame, $node);
         }
 
+        if ($node instanceof MatchExpression) {
+            return $this->reflectMatchExpression($frame, $node);
+        }
+
         if ($node->parent instanceof Attribute) {
             return $this->reflectAttribute($frame, $node->parent);
         }
@@ -112,4 +118,13 @@ class NodeReflector
             $node
         );
     }
+
+    private function reflectMatchExpression(Frame $frame, MatchExpression $node): ReflectionNode
+    {
+        return new ReflectionMatchExpression(
+            $this->services,
+            $frame,
+            $node
+        );
+    }
 }
diff --git a/lib/WorseReflection/Core/Inference/Resolver/MemberAccess/NodeContextFromMemberAccess.php b/lib/WorseReflection/Core/Inference/Resolver/MemberAccess/NodeContextFromMemberAccess.php
index 02ff8ea7..d1480f01 100644
--- a/lib/WorseReflection/Core/Inference/Resolver/MemberAccess/NodeContextFromMemberAccess.php
+++ b/lib/WorseReflection/Core/Inference/Resolver/MemberAccess/NodeContextFromMemberAccess.php
@@ -167,6 +167,10 @@ class NodeContextFromMemberAccess
                     break;
                 }
             }
+            if ($reflection instanceof ReflectionEnum && $memberName === 'cases') {
+                $memberType = TypeFactory::array(TypeFactory::reflectedClass($resolver->reflector(), $reflection->name()));
+                break;
+            }
 
             foreach ($subType->members()->byMemberType($memberTypeName)->byName($memberName) as $member) {
                 // if multiple classes declare a member, always take the "top" one


Test patch:
diff --git a/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/WorseFillMatchArmsTest.php b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/WorseFillMatchArmsTest.php
new file mode 100644
index 00000000..9f8dc6cc
--- /dev/null
+++ b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/WorseFillMatchArmsTest.php
@@ -0,0 +1,57 @@
+<?php
+
+namespace Phpactor\CodeTransform\Tests\Adapter\WorseReflection\Refactor;
+
+use Generator;
+use GlobIterator;
+use Microsoft\PhpParser\Parser;
+use Phpactor\CodeTransform\Adapter\WorseReflection\Refactor\WorseFillMatchArms;
+use Phpactor\CodeTransform\Tests\Adapter\WorseReflection\WorseTestCase;
+use Phpactor\TextDocument\ByteOffset;
+use Phpactor\TextDocument\TextDocumentBuilder;
+use SplFileInfo;
+
+class WorseFillMatchArmsTest extends WorseTestCase
+{
+    /**
+     * @dataProvider provideFill
+     */
+    public function testFill(
+        string $path
+    ): void {
+        if (!version_compare(PHP_VERSION, '8.1', '>=')) {
+            $this->markTestSkipped('Not supported');
+        }
+        [$source, $expected, $offset] = $this->sourceExpectedAndOffset($path);
+
+        $fill = $this->createRefactor($source);
+        $transformed = $fill->refactor(
+            TextDocumentBuilder::create($source)->build(),
+            ByteOffset::fromInt($offset)
+        )->apply($source);
+
+        $this->assertEquals(trim($expected), trim($transformed));
+    }
+
+    /**
+     * @return Generator<string,array{string}>
+     */
+    public function provideFill(): Generator
+    {
+        foreach ((new GlobIterator(__DIR__ . '/fixtures/fillMatchArms*.test')) as $fileInfo) {
+            assert($fileInfo instanceof SplFileInfo);
+            yield $fileInfo->getBasename() => [
+                $fileInfo->getPathname()
+            ];
+        }
+    }
+
+    private function createRefactor(string $source): WorseFillMatchArms
+    {
+        $fill = new WorseFillMatchArms(
+            $this->reflectorForWorkspace($source),
+            new Parser(),
+        );
+        return $fill;
+    }
+}
diff --git a/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/WorseFillObjectTest.php b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/WorseFillObjectTest.php
index 6756ad56..1c809ea4 100644
--- a/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/WorseFillObjectTest.php
+++ b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/WorseFillObjectTest.php
@@ -22,7 +22,7 @@ class WorseFillObjectTest extends WorseTestCase
         [$source, $expected, $offset] = $this->sourceExpectedAndOffset($path);
 
         $fill = $this->createFillObject($source, true, false);
-        $transformed = $fill->fillObject(
+        $transformed = $fill->refactor(
             TextDocumentBuilder::create($source)->build(),
             ByteOffset::fromInt($offset)
         )->apply($source);
@@ -40,7 +40,7 @@ class WorseFillObjectTest extends WorseTestCase
         $expected = $this->workspace()->getContents('nonamed');
 
         $fill = $this->createFillObject($source, false, true);
-        $transformed = $fill->fillObject(
+        $transformed = $fill->refactor(
             TextDocumentBuilder::create($source)->build(),
             ByteOffset::fromInt($offset),
         )->apply($source);
@@ -51,7 +51,7 @@ class WorseFillObjectTest extends WorseTestCase
     public function testOffsetNotObject(): void
     {
         $fill = $this->createFillObject('');
-        $edits = $fill->fillObject(
+        $edits = $fill->refactor(
             TextDocumentBuilder::create('<?php echo "hello";')->build(),
             ByteOffset::fromInt(10)
         );
diff --git a/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_existingCases.test b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_existingCases.test
new file mode 100644
index 00000000..7d109158
--- /dev/null
+++ b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_existingCases.test
@@ -0,0 +1,39 @@
+// File: source
+<?php
+
+namespace Phpactor;
+
+enum Page
+{
+    case AboutUs;
+    case Blog;
+    case Websites;
+}
+
+
+function render(Page $page) {
+    return match ($page) <>{
+        Page::Blog => null,
+    };
+}
+// File: expected
+<?php
+
+namespace Phpactor;
+
+enum Page
+{
+    case AboutUs;
+    case Blog;
+    case Websites;
+}
+
+
+function render(Page $page) {
+    return match ($page) {
+        Page::Blog => null,
+        Page::AboutUs => null,
+        Page::Websites => null,
+        
+    };
+}
diff --git a/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_fill.test b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_fill.test
new file mode 100644
index 00000000..22f15a40
--- /dev/null
+++ b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_fill.test
@@ -0,0 +1,36 @@
+// File: source
+<?php
+
+namespace Phpactor;
+
+enum Page
+{
+    case AboutUs;
+    case Blog;
+    case Websites;
+}
+
+
+function render(Page $page) {
+    return match ($page) <>{};
+}
+// File: expected
+<?php
+
+namespace Phpactor;
+
+enum Page
+{
+    case AboutUs;
+    case Blog;
+    case Websites;
+}
+
+
+function render(Page $page) {
+    return match ($page) {
+    Page::AboutUs => null,
+    Page::Blog => null,
+    Page::Websites => null,
+    };
+}
diff --git a/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_noStartBrace.test b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_noStartBrace.test
new file mode 100644
index 00000000..22c84ffb
--- /dev/null
+++ b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_noStartBrace.test
@@ -0,0 +1,37 @@
+// File: source
+<?php
+
+namespace Phpactor;
+
+enum Page
+{
+    case AboutUs;
+    case Blog;
+    case Websites;
+}
+
+
+function render(Page $page) {
+    return match ($page) <>};
+}
+// File: expected
+<?php
+
+namespace Phpactor;
+
+enum Page
+{
+    case AboutUs;
+    case Blog;
+    case Websites;
+}
+
+
+function render(Page $page) {
+    return match ($page) {
+
+    Page::AboutUs => null,
+    Page::Blog => null,
+    Page::Websites => null,
+    };
+}
diff --git a/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_notEnum.test b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_notEnum.test
new file mode 100644
index 00000000..c114f652
--- /dev/null
+++ b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_notEnum.test
@@ -0,0 +1,16 @@
+// File: source
+<?php
+
+namespace Phpactor;
+
+function render(int $page) {
+    return match ($page) <>};
+}
+// File: expected
+<?php
+
+namespace Phpactor;
+
+function render(int $page) {
+    return match ($page) };
+}
diff --git a/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_unitEnumCase.test b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_unitEnumCase.test
new file mode 100644
index 00000000..3dc6d1db
--- /dev/null
+++ b/lib/CodeTransform/Tests/Adapter/WorseReflection/Refactor/fixtures/fillMatchArms_unitEnumCase.test
@@ -0,0 +1,36 @@
+// File: source
+<?php
+
+namespace Phpactor;
+
+enum Page
+{
+    case AboutUs;
+    case Blog;
+    case Websites;
+}
+
+
+foreach (Page::cases() as $case) {
+    match ($case) <>{};
+}
+// File: expected
+<?php
+
+namespace Phpactor;
+
+enum Page
+{
+    case AboutUs;
+    case Blog;
+    case Websites;
+}
+
+
+foreach (Page::cases() as $case) {
+    match ($case) {
+    Page::AboutUs => null,
+    Page::Blog => null,
+    Page::Websites => null,
+    };
+}
diff --git a/lib/WorseReflection/Tests/Inference/enum/enum_case.test b/lib/WorseReflection/Tests/Inference/enum/enum_case.test
index 650dc8cd..decf5750 100644
--- a/lib/WorseReflection/Tests/Inference/enum/enum_case.test
+++ b/lib/WorseReflection/Tests/Inference/enum/enum_case.test
@@ -9,7 +9,7 @@ wrAssertType('enum(Foo::FOO)', Foo::FOO);
 wrAssertType('<missing>', Foo::FOO->value);
 wrAssertType('string', Foo::FOO->name);
 wrAssertType('"bar"', Foo::BAR);
-wrAssertType('UnitEnumCase[]', Foo::cases());
+wrAssertType('Foo[]', Foo::cases());
 
 class UnitEnumCase {
     public string $name;