Skip to content

Commit a434429

Browse files
committed
Track generics in loops
1 parent 361ace8 commit a434429

3 files changed

Lines changed: 967 additions & 66 deletions

File tree

example.php

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,20 @@ function unionDemo(string|int $value, ?User $maybe): User|null { return $maybe;
396396
$typedArrow = fn(int $x): float => $x * 1.5;
397397

398398

399+
// ── Multi-line @return & Broken Docblock Recovery ───────────────────────────
400+
401+
// Try: collect([])-> ← shows map, groupBy, values, toGroupedArray
402+
// Try: collect([])->map( ← map() resolves correctly despite groupBy's complex @return
403+
// Try: (new BrokenDocRecovery())->broken()-> ← recovers `static`, shows broken() and working()
404+
405+
406+
// ── Foreach over Generic Collection Classes ─────────────────────────────────
407+
// When a class has @extends or @implements with generic type parameters,
408+
// foreach automatically resolves the element type — even without an
409+
// inline @var annotation.
410+
// Open CollectionForeachDemo methods below to try completion inside them.
411+
412+
399413
// ── parent:: Completion ─────────────────────────────────────────────────────
400414
// Open AdminUser's constructor and toArray() in scaffolding below for
401415
// parent:: examples (inherited methods, overridden methods, constants).
@@ -805,6 +819,48 @@ public function demo(): void
805819
}
806820
}
807821

822+
// ── Foreach over Generic Collection Classes ─────────────────────────────────
823+
824+
class CollectionForeachDemo
825+
{
826+
public UserEloquentCollection $users;
827+
828+
public function getUsers(): UserEloquentCollection
829+
{
830+
return new UserEloquentCollection();
831+
}
832+
833+
public function foreachNewCollection(): void
834+
{
835+
$items = new UserEloquentCollection();
836+
foreach ($items as $item) {
837+
$item->getEmail(); // resolves to User via @extends generics
838+
}
839+
}
840+
841+
public function foreachMethodReturn(): void
842+
{
843+
foreach ($this->getUsers() as $user) {
844+
$user->getName(); // resolves via method return type → collection generics
845+
}
846+
}
847+
848+
public function foreachProperty(): void
849+
{
850+
foreach ($this->users as $user) {
851+
$user->getEmail(); // resolves via property type → collection generics
852+
}
853+
}
854+
855+
public function foreachVariable(): void
856+
{
857+
$collection = $this->getUsers();
858+
foreach ($collection as $user) {
859+
$user->getName(); // resolves via variable assignment scanning
860+
}
861+
}
862+
}
863+
808864

809865
// ═══════════════════════════════════════════════════════════════════════════
810866
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
@@ -1509,11 +1565,6 @@ function isRegularUser(mixed $value): bool
15091565
}
15101566

15111567
// ─── Multi-line @return & Broken Docblock Recovery ──────────────────────────
1512-
//
1513-
// PHPantomLSP handles @return types that span multiple docblock lines
1514-
// (common in Laravel's Collection, Eloquent Builder, etc.). When a
1515-
// multi-line @return cannot be fully parsed, the base type is recovered
1516-
// (e.g. `static<…broken` → `static`) so resolution still works.
15171568

15181569
/**
15191570
* @template TKey of array-key
@@ -1578,9 +1629,6 @@ function collect(mixed $value = []): FluentCollection
15781629
return new FluentCollection();
15791630
}
15801631

1581-
// Try: collect([])-> ← shows map, groupBy, values, toGroupedArray
1582-
// Try: collect([])->map( ← map() resolves correctly despite groupBy's complex @return
1583-
15841632
class BrokenDocRecovery
15851633
{
15861634
/**
@@ -1598,4 +1646,27 @@ public function working(): string
15981646
}
15991647
}
16001648

1601-
// Try: (new BrokenDocRecovery())->broken()-> ← recovers `static`, shows broken() and working()
1649+
// ─── Foreach over Generic Collection Classes ────────────────────────────────
1650+
1651+
/**
1652+
* @template TKey of array-key
1653+
* @template-covariant TValue
1654+
* @implements \IteratorAggregate<TKey, TValue>
1655+
*/
1656+
class BaseCollection implements \IteratorAggregate
1657+
{
1658+
/** @return \ArrayIterator<TKey, TValue> */
1659+
public function getIterator(): \ArrayIterator { return new \ArrayIterator([]); }
1660+
}
1661+
1662+
/**
1663+
* @template TKey of array-key
1664+
* @template TModel of Model
1665+
* @extends BaseCollection<TKey, TModel>
1666+
*/
1667+
class EloquentCollection extends BaseCollection {}
1668+
1669+
/**
1670+
* @extends EloquentCollection<int, User>
1671+
*/
1672+
final class UserEloquentCollection extends EloquentCollection {}

0 commit comments

Comments
 (0)