Skip to content

Commit

Permalink
fix: keep original type in collect function in some cases (#1495)
Browse files Browse the repository at this point in the history
  • Loading branch information
canvural committed Dec 29, 2022
1 parent d304b77 commit f5103aa
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/ReturnTypes/Helpers/CollectExtension.php
Expand Up @@ -38,7 +38,7 @@ public function getTypeFromFunctionCall(
FunctionReflection $functionReflection,
FuncCall $functionCall,
Scope $scope
): Type {
): ?Type {
if (count($functionCall->getArgs()) < 1) {
return new GenericObjectType(Collection::class, [new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType()]);
}
Expand Down
28 changes: 11 additions & 17 deletions src/Support/CollectionHelper.php
Expand Up @@ -9,23 +9,20 @@
use Illuminate\Support\Enumerable;
use Iterator;
use IteratorAggregate;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\BenevolentUnionType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeWithClassName;
use Traversable;

final class CollectionHelper
{
public function determineGenericCollectionTypeFromType(Type $type): GenericObjectType
public function determineGenericCollectionTypeFromType(Type $type): ?GenericObjectType
{
$keyType = TypeCombinator::union(new IntegerType(), new StringType());

if ($type instanceof TypeWithClassName) {
if ((new ObjectType(Enumerable::class))->isSuperTypeOf($type)->yes()) {
return $this->getTypeFromEloquentCollection($type);
Expand All @@ -45,23 +42,20 @@ public function determineGenericCollectionTypeFromType(Type $type): GenericObjec
}

if ($type->isIterableAtLeastOnce()->no()) {
return new GenericObjectType(Collection::class, [$keyType, new MixedType()]);
return new GenericObjectType(Collection::class, [new BenevolentUnionType([new IntegerType(), new StringType()]), new MixedType()]);
}

return new GenericObjectType(Collection::class, [
$type->getIterableKeyType()->generalize(GeneralizePrecision::lessSpecific()),
$type->getIterableValueType()->generalize(GeneralizePrecision::lessSpecific()),
]);
return null;
}

private function getTypeFromEloquentCollection(TypeWithClassName $valueType): GenericObjectType
private function getTypeFromEloquentCollection(TypeWithClassName $valueType): ?GenericObjectType
{
$keyType = TypeCombinator::union(new IntegerType(), new StringType());
$keyType = new BenevolentUnionType([new IntegerType(), new StringType()]);

$classReflection = $valueType->getClassReflection();

if ($classReflection === null) {
return new GenericObjectType(Collection::class, [$keyType, new MixedType()]);
return null;
}

$innerValueType = $classReflection->getActiveTemplateTypeMap()->getType('TModel');
Expand All @@ -74,17 +68,17 @@ private function getTypeFromEloquentCollection(TypeWithClassName $valueType): Ge
return new GenericObjectType(Collection::class, [$keyType, $innerValueType]);
}

return new GenericObjectType(Collection::class, [$keyType, new MixedType()]);
return null;
}

private function getTypeFromIterator(TypeWithClassName $valueType): GenericObjectType
private function getTypeFromIterator(TypeWithClassName $valueType): ?GenericObjectType
{
$keyType = TypeCombinator::union(new IntegerType(), new StringType());
$keyType = new BenevolentUnionType([new IntegerType(), new StringType()]);

$classReflection = $valueType->getClassReflection();

if ($classReflection === null) {
return new GenericObjectType(Collection::class, [$keyType, new MixedType()]);
return null;
}

$templateTypes = array_values($classReflection->getActiveTemplateTypeMap()->getTypes());
Expand Down
2 changes: 1 addition & 1 deletion tests/Type/data/collection-generic-static-methods.php
Expand Up @@ -174,7 +174,7 @@
})
);

assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection<(int|string), non-empty-array<string, int|string>>>', collect([
assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection<(int|string), array{id: int, type: string}>>', collect([
[
'id' => 1,
'type' => 'A',
Expand Down
8 changes: 5 additions & 3 deletions tests/Type/data/collection-helper.php
Expand Up @@ -12,13 +12,15 @@
assertType('Illuminate\Support\Collection<0, \'foo\'>', collect('foo'));
assertType('Illuminate\Support\Collection<0, 3.14>', collect(3.14));
assertType('Illuminate\Support\Collection<0, true>', collect(true));
assertType('Illuminate\Support\Collection<int|string, mixed>', collect([]));
assertType('Illuminate\Support\Collection<(int|string), mixed>', collect([]));
assertType('Illuminate\Support\Collection<int, int>', collect([1, 2, 3]));
assertType('Illuminate\Support\Collection<int, string>', collect(['foo', 'bar', 'baz']));
assertType('Illuminate\Support\Collection<int, float>', collect([1.0, 2.0, 3.0]));
assertType('Illuminate\Support\Collection<int, float|int|string>', collect([1, 'foo', 1.0]));
assertType("Illuminate\Support\Collection<int, non-empty-array<int, string>>", collect([['a', 'b', 'c']]));
assertType("Illuminate\Support\Collection<int, non-empty-array<int, string>>", collect([['a', 'b', 'c']])->push(array_fill(0, 3, 'x')));
assertType("Illuminate\Support\Collection<int, array{string, string, string}>", collect([['a', 'b', 'c']]));
assertType("Illuminate\Support\Collection<int, array{string, string, string}>", collect([['a', 'b', 'c']])->push(array_fill(0, 3, 'x')));
assertType("Illuminate\Support\Collection<int, App\User>", collect([new User, new User]));
assertType("Illuminate\Support\Collection<int, array{App\User, App\User, App\User}>", collect([[new User, new User, new User]]));

/** @phpstan-param EloquentCollection<int, int> $eloquentCollection */
function eloquentCollectionInteger(EloquentCollection $eloquentCollection): void
Expand Down

0 comments on commit f5103aa

Please sign in to comment.