Repository: phpactor/phpactor (php)

Issue:
Type information not used in foreach (type is union of empty array and typed array)
```
        $offsets = [];
        $highlights = [];
        foreach ($this->generate($source, $offset) as $highlight) {
            $offsets[] = $highlight->start;
            $offsets[] = $highlight->end;
            $highlights[] = $highlight;
        }

        $highlights; // <- has type array{}/Highlight[]

        $lineCols = EfficientLineCols::fromByteOffsetInts($source, $offsets, true);
        $lspHighlights = [];

        foreach ($highlights as $highlight) { // <- highlight has no type
            $startPos = $lineCols->get($highlight->start);
            $endPos = $lineCols->get($highlight->end);
```

Pull request description:
Gh 1961
Fixes #1961 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a60f709a..76393611 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,10 @@ Features:
 
   - [lsp] Generate mutator @florian-merle
 
+Improvements:
+
+  - [wr] Fix inference of array subscript expressions #1961
+
 Bug fixes:
 
   - [lsp] Do not send workDoneProgress notifications to clients that do not
diff --git a/doc/reference/configuration.rst b/doc/reference/configuration.rst
index 2c6e0c32..18fc20af 100644
--- a/doc/reference/configuration.rst
+++ b/doc/reference/configuration.rst
@@ -843,7 +843,7 @@ FilePathResolverExtension
 """""""""""""""""""""""""""""""""""
 
 
-**Default**: ``"\/home\/mamazu\/packages\/phpactor\/phpactor"``
+**Default**: ``"\/home\/daniel\/www\/phpactor\/phpactor"``
 
 
 .. _param_file_path_resolver.app_name:
diff --git a/lib/Extension/LanguageServerReferenceFinder/Model/Highlighter.php b/lib/Extension/LanguageServerReferenceFinder/Model/Highlighter.php
index 45ab3c2f..8b9e6abb 100644
--- a/lib/Extension/LanguageServerReferenceFinder/Model/Highlighter.php
+++ b/lib/Extension/LanguageServerReferenceFinder/Model/Highlighter.php
@@ -45,7 +45,6 @@ class Highlighter
         $lspHighlights = [];
 
         foreach ($highlights as $highlight) {
-            assert($highlight instanceof Highlight);
             $startPos = $lineCols->get($highlight->start);
             $endPos = $lineCols->get($highlight->end);
             $lspHighlights[] = new DocumentHighlight(
diff --git a/lib/WorseReflection/Bridge/TolerantParser/Reflection/TraitImport/TraitAlias.php b/lib/WorseReflection/Bridge/TolerantParser/Reflection/TraitImport/TraitAlias.php
index 71a22350..67a416f5 100644
--- a/lib/WorseReflection/Bridge/TolerantParser/Reflection/TraitImport/TraitAlias.php
+++ b/lib/WorseReflection/Bridge/TolerantParser/Reflection/TraitImport/TraitAlias.php
@@ -8,7 +8,7 @@ class TraitAlias
 {
     public function __construct(
         private string $originalName,
-        private ?Visibility $visiblity = null,
+        private ?Visibility $visiblity,
         private string $newName
     ) {
     }
diff --git a/lib/WorseReflection/Core/Inference/Resolver/AssignmentExpressionResolver.php b/lib/WorseReflection/Core/Inference/Resolver/AssignmentExpressionResolver.php
index 1fa7d5fa..155a52a8 100644
--- a/lib/WorseReflection/Core/Inference/Resolver/AssignmentExpressionResolver.php
+++ b/lib/WorseReflection/Core/Inference/Resolver/AssignmentExpressionResolver.php
@@ -187,11 +187,19 @@ class AssignmentExpressionResolver implements Resolver
                     continue;
                 }
 
-                // array addition `$foo[] = `
                 // @phpstan-ignore-next-line TP lies
+                if ($rightContext->type() instanceof Literal) {
+                    $frame->locals()->set(
+                        $variable->withType(
+                            $type->add($rightContext->type())
+                        )->withOffset($leftOperand->getStartPosition())
+                    );
+                    continue;
+                }
+
                 $frame->locals()->set(
                     $variable->withType(
-                        $type->add($rightContext->type())
+                        TypeFactory::array($rightContext->type())
                     )->withOffset($leftOperand->getStartPosition())
                 );
             }
diff --git a/lib/WorseReflection/Core/Inference/Resolver/ForeachStatementResolver.php b/lib/WorseReflection/Core/Inference/Resolver/ForeachStatementResolver.php
index b5edd7d1..853fb7e6 100644
--- a/lib/WorseReflection/Core/Inference/Resolver/ForeachStatementResolver.php
+++ b/lib/WorseReflection/Core/Inference/Resolver/ForeachStatementResolver.php
@@ -214,8 +214,9 @@ class ForeachStatementResolver implements Resolver
                     continue;
                 }
                 if ($previous = $frame->locals()->lessThan($local->offset())->byName($local->name())->lastOrNull()) {
+                    $type = $previous->type()->addType($local->type())->reduce();
                     $frame->locals()->set(
-                        $previous->withType($previous->type()->addType($local->type()))->withOffset($compoundStatement->closeBrace->getEndPosition())
+                        $previous->withType($type)->withOffset($compoundStatement->closeBrace->getEndPosition())
                     );
                 }
             }
diff --git a/lib/WorseReflection/Core/Type.php b/lib/WorseReflection/Core/Type.php
index e6698f94..6ddc55ee 100644
--- a/lib/WorseReflection/Core/Type.php
+++ b/lib/WorseReflection/Core/Type.php
@@ -219,4 +219,12 @@ abstract class Type
     {
         return $mapper($this);
     }
+
+    /**
+     * If this type can "consume" or replace the given type
+     */
+    public function consumes(Type $type2): Trinary
+    {
+        return Trinary::maybe();
+    }
 }
diff --git a/lib/WorseReflection/Core/Type/AggregateType.php b/lib/WorseReflection/Core/Type/AggregateType.php
index e0a486c8..198cc243 100644
--- a/lib/WorseReflection/Core/Type/AggregateType.php
+++ b/lib/WorseReflection/Core/Type/AggregateType.php
@@ -70,7 +70,25 @@ abstract class AggregateType extends Type
             return TypeFactory::nullable($this->stripNullable());
         }
 
-        return $this;
+        $remove = [];
+        foreach ($this->types as $type1) {
+            foreach ($this->types as $type2) {
+                if ($type1 === $type2) {
+                    continue;
+                }
+
+                if ($type1->consumes($type2)->isTrue()) {
+                    $remove[] = $type2;
+                }
+            }
+        }
+
+        $type = $this;
+        foreach ($remove as $removeType) {
+            $type = $this->remove($removeType);
+        }
+
+        return $type;
     }
 
     abstract public function withTypes(Type ...$types): AggregateType;
diff --git a/lib/WorseReflection/Core/Type/ArrayType.php b/lib/WorseReflection/Core/Type/ArrayType.php
index bf3583b3..dd9b2dd5 100644
--- a/lib/WorseReflection/Core/Type/ArrayType.php
+++ b/lib/WorseReflection/Core/Type/ArrayType.php
@@ -3,6 +3,7 @@
 namespace Phpactor\WorseReflection\Core\Type;
 
 use Closure;
+use Phpactor\WorseReflection\Core\Trinary;
 use Phpactor\WorseReflection\Core\Type;
 
 class ArrayType extends PseudoIterableType implements IterableType, HasEmptyType
@@ -36,4 +37,14 @@ class ArrayType extends PseudoIterableType implements IterableType, HasEmptyType
     {
         return new ArrayLiteral([]);
     }
+
+    public function consumes(Type $type): Trinary
+    {
+        // if type is an empty array, replace with this one
+        if ($type instanceof ArrayLiteral && count($type->types()) === 0) {
+            return Trinary::true();
+        }
+
+        return Trinary::maybe();
+    }
 }
diff --git a/lib/WorseReflection/Core/Type/UnionType.php b/lib/WorseReflection/Core/Type/UnionType.php
index f50d3333..5d041923 100644
--- a/lib/WorseReflection/Core/Type/UnionType.php
+++ b/lib/WorseReflection/Core/Type/UnionType.php
@@ -6,7 +6,7 @@ use Phpactor\WorseReflection\Core\Trinary;
 use Phpactor\WorseReflection\Core\Type;
 use Phpactor\WorseReflection\Core\TypeFactory;
 
-final class UnionType extends AggregateType
+final class UnionType extends AggregateType implements Generalizable
 {
     public function __toString(): string
     {


Test patch:
diff --git a/lib/WorseReflection/Tests/Inference/array-creation-expression/test1.test b/lib/WorseReflection/Tests/Inference/array-creation-expression/test1.test
deleted file mode 100644
index e69de29b..00000000
diff --git a/lib/WorseReflection/Tests/Inference/assignment/array_add_in_foreach.test b/lib/WorseReflection/Tests/Inference/assignment/array_add_in_foreach.test
new file mode 100644
index 00000000..b92a68a1
--- /dev/null
+++ b/lib/WorseReflection/Tests/Inference/assignment/array_add_in_foreach.test
@@ -0,0 +1,25 @@
+<?php
+
+class Bar {}
+
+/**
+ * @return Generator<Bar>
+ */
+function bar(): Generator {
+}
+
+$highlights = [];
+foreach (bar() as $highlight) {
+    wrAssertType("Bar", $highlight);
+    $highlights[] = $highlight;
+}
+
+
+wrAssertType("Bar[]", $highlights);
+
+$lineCols = EfficientLineCols::fromByteOffsetInts($source, $offsets, true);
+$lspHighlights = [];
+
+foreach ($highlights as $highlight) {
+    wrAssertType("Bar", $highlight);
+}
diff --git a/lib/WorseReflection/Tests/Inference/foreach/preserve_types_after_break.test b/lib/WorseReflection/Tests/Inference/foreach/preserve_types_after_break.test
index f4576ead..eb262b6c 100644
--- a/lib/WorseReflection/Tests/Inference/foreach/preserve_types_after_break.test
+++ b/lib/WorseReflection/Tests/Inference/foreach/preserve_types_after_break.test
@@ -28,7 +28,7 @@ class NoBackgroundWithSingleScenarioRule
             }
         }
 
-        wrAssertType('string|null', $background);
+        wrAssertType('?string', $background);
     }
 }