Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: keep original type in collect function in some cases #1495

Merged
merged 1 commit into from Dec 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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