From d304b77e27111ce133c53cc374f9aa637cc705fc Mon Sep 17 00:00:00 2001 From: Brad Miller <28307684+mad-briller@users.noreply.github.com> Date: Thu, 29 Dec 2022 10:57:47 +0000 Subject: [PATCH] fix: `make`, `makeWith` and `resolve` methods not resolving to correct type on Application and Container. (#1451) --- CHANGELOG.md | 1 + extension.neon | 13 +++++ .../AppMakeDynamicReturnTypeExtension.php | 39 ++------------- src/ReturnTypes/AppMakeHelper.php | 50 +++++++++++++++++++ ...licationMakeDynamicReturnTypeExtension.php | 35 +++++++++++++ ...ontainerMakeDynamicReturnTypeExtension.php | 35 +++++++++++++ src/ReturnTypes/Helpers/AppExtension.php | 38 +++----------- tests/Type/GeneralTypeTest.php | 2 + tests/Type/data/application-make.php | 16 ++++++ tests/Type/data/container-make.php | 16 ++++++ 10 files changed, 179 insertions(+), 66 deletions(-) create mode 100644 src/ReturnTypes/AppMakeHelper.php create mode 100644 src/ReturnTypes/ApplicationMakeDynamicReturnTypeExtension.php create mode 100644 src/ReturnTypes/ContainerMakeDynamicReturnTypeExtension.php create mode 100644 tests/Type/data/application-make.php create mode 100644 tests/Type/data/container-make.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 21c29c208..b79e17688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - fix: changed dependency from `Illuminate\Config\Repository` to Config interface by @nai4rus - feat: assert `view-string` when using `view()->exists()` by @mad-briller +- feat: Add return-type extension for Application / Container `make` `makeWith` and `resolve` methods by @mad-briller. - feat: freed `joinSub` to allow models to join other models by @harmenjanssen in https://github.com/nunomaduro/larastan/pull/1352 - feat: updated return type of the Request::header method by @mad-briller - feat: Added stub for `optional()` helper and class by @mad-briller in https://github.com/nunomaduro/larastan/pull/1344 diff --git a/extension.neon b/extension.neon index 95b44c3f3..52a68b159 100644 --- a/extension.neon +++ b/extension.neon @@ -482,6 +482,19 @@ services: class: NunoMaduro\Larastan\Support\ViewFileHelper arguments: viewDirectories: %viewDirectories% + + - + class: NunoMaduro\Larastan\ReturnTypes\ApplicationMakeDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - + class: NunoMaduro\Larastan\ReturnTypes\ContainerMakeDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + + - NunoMaduro\Larastan\ReturnTypes\AppMakeHelper + rules: - NunoMaduro\Larastan\Rules\UselessConstructs\NoUselessWithFunctionCallsRule - NunoMaduro\Larastan\Rules\UselessConstructs\NoUselessValueFunctionCallsRule diff --git a/src/ReturnTypes/AppMakeDynamicReturnTypeExtension.php b/src/ReturnTypes/AppMakeDynamicReturnTypeExtension.php index a7c4218f4..640d650f6 100644 --- a/src/ReturnTypes/AppMakeDynamicReturnTypeExtension.php +++ b/src/ReturnTypes/AppMakeDynamicReturnTypeExtension.php @@ -5,23 +5,18 @@ namespace NunoMaduro\Larastan\ReturnTypes; use Illuminate\Support\Facades\App; -use NunoMaduro\Larastan\Concerns\HasContainer; -use PhpParser\Node\Expr\ClassConstFetch; use PhpParser\Node\Expr\StaticCall; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\Scalar\String_; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\DynamicStaticMethodReturnTypeExtension; -use PHPStan\Type\ErrorType; -use PHPStan\Type\NeverType; -use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use Throwable; final class AppMakeDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { - use HasContainer; + public function __construct( + private AppMakeHelper $appMakeHelper + ) { + } public function getClass(): string { @@ -35,30 +30,6 @@ public function isStaticMethodSupported(MethodReflection $methodReflection): boo public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type { - if (count($methodCall->getArgs()) === 0) { - return new ErrorType(); - } - - $expr = $methodCall->getArgs()[0]->value; - if ($expr instanceof String_) { - try { - /** @var object|null $resolved */ - $resolved = $this->resolve($expr->value); - - if ($resolved === null) { - return new ErrorType(); - } - - return new ObjectType(get_class($resolved)); - } catch (Throwable $exception) { - return new ErrorType(); - } - } - - if ($expr instanceof ClassConstFetch && $expr->class instanceof FullyQualified) { - return new ObjectType($expr->class->toString()); - } - - return new NeverType(); + return $this->appMakeHelper->resolveTypeFromCall($methodCall); } } diff --git a/src/ReturnTypes/AppMakeHelper.php b/src/ReturnTypes/AppMakeHelper.php new file mode 100644 index 000000000..29b48200c --- /dev/null +++ b/src/ReturnTypes/AppMakeHelper.php @@ -0,0 +1,50 @@ +getArgs()) === 0) { + return new ErrorType(); + } + + $expr = $call->getArgs()[0]->value; + if ($expr instanceof String_) { + try { + /** @var object|null $resolved */ + $resolved = $this->resolve($expr->value); + + if ($resolved === null) { + return new ErrorType(); + } + + return new ObjectType(get_class($resolved)); + } catch (Throwable $exception) { + return new ErrorType(); + } + } + + if ($expr instanceof ClassConstFetch && $expr->class instanceof FullyQualified) { + return new ObjectType($expr->class->toString()); + } + + return new NeverType(); + } +} diff --git a/src/ReturnTypes/ApplicationMakeDynamicReturnTypeExtension.php b/src/ReturnTypes/ApplicationMakeDynamicReturnTypeExtension.php new file mode 100644 index 000000000..e9e622331 --- /dev/null +++ b/src/ReturnTypes/ApplicationMakeDynamicReturnTypeExtension.php @@ -0,0 +1,35 @@ +getName(), ['make', 'makeWith', 'resolve'], true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + return $this->appMakeHelper->resolveTypeFromCall($methodCall); + } +} diff --git a/src/ReturnTypes/ContainerMakeDynamicReturnTypeExtension.php b/src/ReturnTypes/ContainerMakeDynamicReturnTypeExtension.php new file mode 100644 index 000000000..d0037b52d --- /dev/null +++ b/src/ReturnTypes/ContainerMakeDynamicReturnTypeExtension.php @@ -0,0 +1,35 @@ +getName(), ['make', 'makeWith', 'resolve'], true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + return $this->appMakeHelper->resolveTypeFromCall($methodCall); + } +} diff --git a/src/ReturnTypes/Helpers/AppExtension.php b/src/ReturnTypes/Helpers/AppExtension.php index ef09164b7..f0722372b 100644 --- a/src/ReturnTypes/Helpers/AppExtension.php +++ b/src/ReturnTypes/Helpers/AppExtension.php @@ -5,24 +5,20 @@ namespace NunoMaduro\Larastan\ReturnTypes\Helpers; use Illuminate\Foundation\Application; -use NunoMaduro\Larastan\Concerns\HasContainer; -use PhpParser\Node\Expr; -use PhpParser\Node\Expr\ClassConstFetch; +use NunoMaduro\Larastan\ReturnTypes\AppMakeHelper; use PhpParser\Node\Expr\FuncCall; -use PhpParser\Node\Name\FullyQualified; -use PhpParser\Node\Scalar\String_; use PHPStan\Analyser\Scope; use PHPStan\Reflection\FunctionReflection; use PHPStan\Type\DynamicFunctionReturnTypeExtension; -use PHPStan\Type\ErrorType; -use PHPStan\Type\NeverType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; -use Throwable; class AppExtension implements DynamicFunctionReturnTypeExtension { - use HasContainer; + public function __construct( + private AppMakeHelper $appMakeHelper + ) { + } public function isFunctionSupported(FunctionReflection $functionReflection): bool { @@ -38,28 +34,6 @@ public function getTypeFromFunctionCall( return new ObjectType(Application::class); } - /** @var Expr $expr */ - $expr = $functionCall->getArgs()[0]->value; - - if ($expr instanceof String_) { - try { - /** @var object|null $resolved */ - $resolved = $this->resolve($expr->value); - - if ($resolved === null) { - return new ErrorType(); - } - - return new ObjectType(get_class($resolved)); - } catch (Throwable $exception) { - return new ErrorType(); - } - } - - if ($expr instanceof ClassConstFetch && $expr->class instanceof FullyQualified) { - return new ObjectType($expr->class->toString()); - } - - return new NeverType(); + return $this->appMakeHelper->resolveTypeFromCall($functionCall); } } diff --git a/tests/Type/GeneralTypeTest.php b/tests/Type/GeneralTypeTest.php index 271e5d6d0..73eecf235 100644 --- a/tests/Type/GeneralTypeTest.php +++ b/tests/Type/GeneralTypeTest.php @@ -35,6 +35,8 @@ public function dataFileAsserts(): iterable yield from $this->gatherAssertTypes(__DIR__.'/data/database-transaction.php'); yield from $this->gatherAssertTypes(__DIR__.'/data/container-array-access.php'); yield from $this->gatherAssertTypes(__DIR__.'/data/view-exists.php'); + yield from $this->gatherAssertTypes(__DIR__.'/data/application-make.php'); + yield from $this->gatherAssertTypes(__DIR__.'/data/container-make.php'); } /** diff --git a/tests/Type/data/application-make.php b/tests/Type/data/application-make.php new file mode 100644 index 000000000..05b22ecc6 --- /dev/null +++ b/tests/Type/data/application-make.php @@ -0,0 +1,16 @@ +make(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $app->makeWith(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $app->resolve(Repository::class)); + +/** @var \Illuminate\Contracts\Foundation\Application $app */ +assertType('Illuminate\Contracts\Config\Repository', $app->make(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $app->makeWith(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $app->resolve(Repository::class)); diff --git a/tests/Type/data/container-make.php b/tests/Type/data/container-make.php new file mode 100644 index 000000000..deff1292e --- /dev/null +++ b/tests/Type/data/container-make.php @@ -0,0 +1,16 @@ +make(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $container->makeWith(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $container->resolve(Repository::class)); + +/** @var \Illuminate\Contracts\Container\Container $container */ +assertType('Illuminate\Contracts\Config\Repository', $container->make(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $container->makeWith(Repository::class)); +assertType('Illuminate\Contracts\Config\Repository', $container->resolve(Repository::class));