Skip to content

Commit

Permalink
fix more stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
canvural committed Oct 11, 2024
1 parent adabf77 commit e334bda
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 10 deletions.
70 changes: 62 additions & 8 deletions src/Types/RelationDynamicMethodReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,32 @@

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Larastan\Larastan\Internal\LaravelVersion;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Identifier;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Reflection\ReflectionProvider;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\StaticType;
use PHPStan\Type\Type;

use function array_map;
use function array_slice;
use function array_values;
use function count;
use function in_array;
use function version_compare;

class RelationDynamicMethodReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
public function __construct(private ReflectionProvider $provider)
{
}

public function getClass(): string
{
return Model::class;
Expand Down Expand Up @@ -59,6 +65,10 @@ public function getTypeFromMethodCall(

$classNames = $returnType->getObjectClassNames();

if (! LaravelVersion::hasLaravel1115Generics()) {
return $this->getTypeForLaravelLessThan1115($methodReflection, $methodCall, $scope, $returnType, $classNames);
}

if (count($classNames) !== 1) {
return null;
}
Expand Down Expand Up @@ -98,14 +108,58 @@ public function getTypeFromMethodCall(
$types = array_map(static fn ($model) => new ObjectType((string) $model), $models);
$types[] = $scope->getType($methodCall->var);

if (
version_compare(LARAVEL_VERSION, '11.0.0', '<')
&& ! (new ObjectType(BelongsTo::class))->isSuperTypeOf($returnType)->yes()
) {
// Only BelongsTo has more than one type
$types = [$types[0]];
return new GenericObjectType($classNames[0], $types);
}

/**
* @param string[] $classNames
*
* @throws ShouldNotHappenException
*/
private function getTypeForLaravelLessThan1115(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope,
Type $returnType,
array $classNames,
): Type|null {
if (count($classNames) !== 1) {
return null;
}

return new GenericObjectType($classNames[0], $types);
$calledOnType = $scope->getType($methodCall->var);

if ($calledOnType instanceof StaticType) {
$calledOnType = new ObjectType($calledOnType->getClassName());
}

if (count($methodCall->getArgs()) === 0) {
// Special case for MorphTo. `morphTo` can be called without arguments.
if ($methodReflection->getName() === 'morphTo') {
return new GenericObjectType($classNames[0], [new ObjectType(Model::class), $calledOnType]);
}

return null;
}

$argType = $scope->getType($methodCall->getArgs()[0]->value);
$argStrings = $argType->getConstantStrings();

if (count($argStrings) !== 1) {
return null;
}

$argClassName = $argStrings[0]->getValue();

if (! $this->provider->hasClass($argClassName)) {
$argClassName = Model::class;
}

// Special case for BelongsTo. We need to add the child model as a generic type also.
if ((new ObjectType(BelongsTo::class))->isSuperTypeOf($returnType)->yes()) {
return new GenericObjectType($classNames[0], [new ObjectType($argClassName), $calledOnType]);
}

return new GenericObjectType($classNames[0], [new ObjectType($argClassName)]);
}
}
4 changes: 2 additions & 2 deletions stubs/common/Enumerable.stub
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ interface Enumerable extends \Countable, \IteratorAggregate, \JsonSerializable
*
* @template TNewKey of array-key
*
* @param (callable(TValue, TKey=): TNewKey)|array<int, mixed>|string $keyBy
* @param (callable(TValue, TKey): TNewKey)|array<array-key, mixed>|string $keyBy
* @return static<($keyBy is string ? array-key : ($keyBy is array<int, mixed> ? array-key : TNewKey)), TValue>
*/
public function keyBy($keyBy);
Expand All @@ -54,7 +54,7 @@ interface Enumerable extends \Countable, \IteratorAggregate, \JsonSerializable
*
* @template TGroupKey of array-key
*
* @param (callable(TValue, TKey=): TGroupKey)|array<int, mixed>|string $groupBy
* @param (callable(TValue, TKey): TGroupKey)|array<array-key, mixed>|string $groupBy
* @param bool $preserveKeys
* @return static<($groupBy is string ? array-key : ($groupBy is array<int, mixed> ? array-key : TGroupKey)), static<($preserveKeys is true ? TKey : int), TValue>>
*/
Expand Down

0 comments on commit e334bda

Please sign in to comment.